[Spring] AWS S3 Multipart Upload - REST API

Sangwoo Park·2022년 3월 26일
0

Multipart Upload

목록 보기
1/2
post-custom-banner

개요

대용량 파일 업로드를 구현하기 위해서 구글링을 해본 결과 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 bucket에 해당 업로드 건이 등록 되고, unique ID를 응답 값으로 준다.

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

이때 KEY는 AWS S3에 올라갈 파일의 이름이다. ex) dir/object.mp4

그리고 다음 단계에 앞서 parts upload 단계에서 받을 ETag들을 담을 List를 만든다.

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

2. Parts upload

업로드 할 파일을 chunk로 쪼개서 각각 요청을 보내는 단계다.
각 요청마다 chunk part를 보내고 ETag 값을 반환받는다.
이 ETag들을 모아서 complete할 때 보내주어야 업로드에 성공할 수 있다.
아래는 각 part들을 업로드 할 때 사용되는 rule 이다.

  • 각 요청에는 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에서 받은 upload 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) // 필요없는 upload들을 폐기
}

4. 기타

  • part들 업로드 중간에 한번이라도 멈추면 해당 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

profile
going up
post-custom-banner

0개의 댓글