[Spring] AWS S3 Multipart Upload - REST API

16194 단어 JavaS3awsSpringJava

대용량 파일 업로드를 구현하기 위해서 구글링을 해본 결과 AWS S3에서 대용량 파일 업로드시 Multipart Upload를 지원한다는 문서를 보았다. 하지만 그 공식 문서 외에는 구글링으로 이해할 수 있는 자료를 찾기가 쉽지 않았고, 몇일간 여러가지 시도를 해보다 마침내 성공해서 정리할 겸 글을 남긴다. 참고로 필자의 개발환경은 Java Spring MVC이다.

우선 내가 구현하고 싶었던 기능은 슬랙, 카카오톡 등에 파일을 업로드하면 progress 상태가 보이면서 순차적으로 파일이 업로드되는 기능이었고, 예상되는 파일 업로드 크기는 최대 10GB 이다.


AWS 공식문서에 따르면, 멀티파트 업로드를 위해서는 3가지 단계가 필요하다.

  1. Multipart upload initiation
  2. Parts upload
  3. Multipart upload completion

1. Multipart upload initiation

AWS에 initiate request를 보내면 S3에 해당 업로드가 등록이 되고, unique id를 보내준다.

InitiateMultipartUploadResult initiateUpload =
                s3Client.initiateMultipartUpload(
                        new InitiateMultipartUploadRequest(BUCKET_NAME, KEY));
log.info("upload id : {}", initiateUpload.getUploadId());

이때 KEY는 AWS S3에 올라갈 파일의 이름이다. ex) dir/object.mp4
그리고 parts upload 단계에서 받을 ETag들을 담을 배열을 만든다.

private List<PartETag> partETags = new ArrayList<>();

2. Parts upload

업로드 할 파일을 chunk로 쪼개서 각각 요청을 보낸다.
각 요청마다 해당 part에 해당하는 ETag 값을 반환받고,
이 ETag들을 모아서 complete할 때 보내주어야 한다.

  • 각 요청에는 initiation에서 받은 unique id를 포함해야 한다.
  • 각 파트마다 파트 번호를 명시해야한다. 파트번호는 1부터 10,000까지 가능하고, 연속적일 필요는 없으며 순차적으로 증가하기만 하면 된다. 예를 들어 파트 번호들이 [1, 2, 3, 4, 5] 가 아니라 [1, 3, 5, 7, 9] 여도 문제없이 동작한다.
  • 각각의 파트는 최소 5MB 이상이어야 한다. (마지막 파트 제외. 마지막 파트는 5MB 이하 가능)
  • 이 때 5MB는 5MiB = 5 * 1024 * 1024 byte 이다.
UploadPartRequest uploadPartRequest = new UploadPartRequest()
                    .withBucketName(BUCKET_NAME) // 버킷 이름
                    .withKey(KEY) // s3에 올라갈 파일 경로+이름
                    .withUploadId(upload_id) // init에서 받은 id
                    .withPartNumber(part_number) // 순차적인 part number (1~10,000)
                    .withFile(part_file) // chunk file
                    .withPartSize(part_file.length()); // chunk file size

UploadPartResult uploadPartResult = s3Client.uploadPart(uploadPartRequest);
partETags.add(uploadPartResult.getPartETag()); // init때 만든 배열에 ETag 담기

3. Multipart upload completion

모든 파트의 업로드가 완료되면 마지막으로 AWS에 완료요청을 보내서 파일 저장을 마무리한다. complete 요청을 보내지 않으면 파일이 생성되지 않는다.

s3Client.completeMultipartUpload(
                    new CompleteMultipartUploadRequest(
                            BUCKET_NAME,
                            KET,
                            upload_id,
                            partETags)); // List<PartEtag>

complete 이후에 S3 Bucket을 확인해보면 파일이 잘 담겨있는것을 확인할 수 있다.


💡 참고사항

1. Front code

프론트에서 파일을 Chunk로 자르기 위해 사용한 코드

const CHUNK_SIZE = 5 * 1024 * 1024; // 5MiB
let part_index = 1, flag = 0;
const origin_file = input_file_element.files[0];

function partUpload(upload_id) {
  const blob = origin_file.slice(flag, Math.min(origin_file.size, flag + CHUNK_SIZE));
  const reader = new FileReader();
  reader.readAsDataURL(blob);
  reader.onloadend = async () => {
    if (flag >= origin_file.size) { // 전송완료
      const completed = await completeUpload(upload_id);
      return;
    }

    const success = await sendEncodedByteData(upload_id, reader.result);
    if (success) { // 전송 성공
      byte_flag += CHUNK_SIZE;
      part_index += 1;
      partUpload(upload_id); // recursive
    } else { // 전송중 에러
      // error handle
    }
  }
}

서버로 보낼때는 Base64로 Encoding 되기 때문에,
컨트롤러에서는 String으로 받은 후에 Base64 Decoding을 통해 byte[] 로 변환했다.

2. Abort

initiate 이후에 에러발생, 업로드 중단 등의 이유로 complete가 되지 않는다면 해당 upload id의 multipart upload는 S3에 계속 남아있게 된다. 그렇게 되면 요금이 부과될 수도 있으므로 꼭 complete 혹은 abort를 하라고 AWS 문서에 나와있다.

s3Client.abortMultipartUpload(
	new AbortMultipartUploadRequest(
    BUCKET_NAME,
    KEY, 
    upload_id)); // return void

3. 남은 업로드 조회하기

개발 테스트중에 미처 complete, abort 하지 못한 업로드들을 처리해주어야 했다.

MultipartUploadListing multipartUploadListing = 
	s3Client.listMultipartUploads(new ListMultipartUploadsRequest(BUCKET_NAME);

List<MultipartUpload> multipartUploads = multipartUploadListing.getMultipartUploads();
for (MultipartUpload multipartUpload : multipartUploads) {
	log.info("upload id: {}, key: {}", multipartUpload.getUploadId(), multipartUpload.getKey());
    // abort(upload id, key)
}

4. 기타

  • 중간에 한번이라도 멈추거나 하면 해당 upload id는 사용 불가능해진다고 한다.
  • 중간까지 업로드 한 후 complete 해버리면, 업로드된 동영상 파일은 업로드한 지점까지만 재생 가능하다.
  • 각 파트파일의 크기는 최소 5MB~5GB 라고 하니 최대 5GiB * 10,000 = 5TB까지 업로드가 가능하다.
  • 현재는 중단시 해당 파일 업로드는 폐기되도록 구현했지만, 기능들을 잘 활용하면 대용량 파일 업로드의 일시정지, 재개 기능도 구현이 가능할 것 같다.
  • 서버 측 Heap Size의 안정성 테스트 및 관리가 필요한 것 같다.
  • 멀티스레드를 이용하여 파일 업로드의 속도를 증가시킬 수 있을 것 같다.
  • AWS JavaScript SDK를 이용하여 front 단에서 바로 업로드를 하면 서버 부하를 상당부분 완화할 수 있을 것 같다.

Reference :
AWS S3 Multipart upload doc
https://wave1994.tistory.com/152

좋은 웹페이지 즐겨찾기