マルチパートアップロードとETagの算数
한국어 원문으로 표시합니다.
한 줄 요약
멀티파트 ETag 는 내용의 md5 가 아니다. 각 파트 md5 의 이진값을 이어 붙여 다시 md5 한 뒤 -파트수 를 붙인 값이다.
왜 이게 필요했나
5GB 파일을 한 번의 PUT 으로 올리다가 90% 지점에서 연결이 끊기면 처음부터 다시 해야 합니다. 그리고 하나의 TCP 연결로는 대역폭을 다 쓰지 못합니다.
멀티파트 업로드가 이 둘을 해결합니다. 업로드를 시작해 UploadId 를 받고, 파일을 파트로 나눠 병렬로 올리고, 마지막에 파트 목록으로 완료를 알립니다. 중간에 끊기면 그 파트만 다시 올리면 됩니다.
어떻게 동작하나
규칙에 숫자가 있습니다. 파트는 최대 10,000개이고, 마지막 파트를 제외한 각 파트는 최소 5MiB 여야 합니다. 파트 최대 크기는 5GiB 입니다. 이 셋을 곱하면 단일 객체의 최대 크기가 나옵니다 — 5GiB x 10,000 = 약 5TB 입니다.
파트 크기 선택은 트레이드오프입니다. 작으면 요청 수가 늘고(요청당 과금이 있는 서비스에서는 비용), 크면 재전송 비용이 커집니다. 실무에서는 8~64MiB 사이를 많이 씁니다. 파일 크기를 10,000 으로 나눈 값보다는 커야 합니다.
ETag 계산이 흥미로운 지점입니다. 단일 파트면 내용의 md5 그대로입니다. 멀티파트면 이렇게 계산합니다.
1. 각 파트의 md5 를 이진(16바이트)으로 구한다
2. 그것들을 순서대로 이어 붙인다 (파트 5개면 80바이트)
3. 그 80바이트의 md5 를 구한다
4. 뒤에 "-5" 를 붙인다
그래서 ETag 에 하이픈이 있으면 멀티파트로 올라간 객체이고, 로컬 파일의 md5 와 절대 같지 않습니다. "ETag 로 무결성 검증했는데 안 맞는다"는 문의의 대부분이 이것입니다. 검증하려면 같은 파트 크기로 나눠 같은 계산을 해야 합니다.
파트 크기를 정하는 계산
파트 크기는 감으로 정하지 않습니다. 두 제약이 상한과 하한을 정해 줍니다.
하한 = 파일 크기 ÷ 10,000 (파트 수 제한)
하한 = 최소 5 MiB (마지막 파트 제외)
상한 = 5 GiB
100GB 파일이면 → 100GB ÷ 10,000 = 10MB 이상이어야 한다
→ 16MiB 로 잡으면 6,400 파트. 여유가 있다
그다음 재전송 비용을 봅니다. 네트워크가 불안정해 평균 2% 의 파트가 실패한다면, 파트가 클수록 한 번 실패에 버리는 바이트가 큽니다. 반대로 파트가 작으면 요청 수가 늘어 오버헤드와 요청 과금이 늘어납니다.
| 파일 크기 | 권장 파트 | 파트 수 | 비고 |
|---|---|---|---|
| ~100MB | 멀티파트 불필요 | 1 | 단일 PUT 이 간단하다 |
| 100MB~1GB | 8~16 MiB | 12~64 | 병렬 4~8이면 충분 |
| 1GB~50GB | 32~64 MiB | 30~800 | 병렬 8~16 |
| 50GB 이상 | 64~128 MiB | 400~800 | 파트 수 상한을 먼저 확인 |
병렬도는 대역폭과 메모리로 정합니다. 파트 크기 × 병렬도만큼 메모리에 올라가므로, 64MiB × 16 = 1GB 입니다. 컨테이너 메모리 한도를 넘기면 OOM 으로 죽습니다.
체크섬으로 무결성을 확인하는 바른 방법
ETag 로 무결성을 확인하려는 시도는 멀티파트에서 무너집니다. 대신 S3 가 제공하는 추가 체크섬 을 씁니다. 업로드할 때 알고리즘을 지정하면 S3 가 파트별로 검증하고 객체 전체의 체크섬도 보관합니다.
s3.put_object(Bucket=b, Key=k, Body=data, ChecksumAlgorithm="SHA256")
r = s3.head_object(Bucket=b, Key=k, ChecksumMode="ENABLED")
print(r.get("ChecksumSHA256"))
멀티파트에서는 이 값도 합성 방식이지만(파트 체크섬을 이어 붙여 다시 해시), S3 가 파트를 받을 때마다 검증 하므로 전송 중 손상은 그 자리에서 잡힙니다. ETag 를 손으로 재계산하는 것보다 훨씬 안전합니다.
CRC32C 는 계산이 빨라 대용량에 유리하고, SHA256 은 느리지만 암호학적으로 강합니다. 전송 오류 검출이 목적이면 CRC32C 로 충분합니다.
중단된 업로드를 찾아 정리하기
# 이 버킷에 떠도는 미완료 업로드
aws s3api list-multipart-uploads --bucket my-bucket
# 하나 지우기 — 올라간 파트가 모두 사라진다
aws s3api abort-multipart-upload --bucket my-bucket --key big.tar --upload-id <UploadId>
수동으로 찾는 것은 잊습니다. 수명주기 규칙으로 자동화하는 것이 정답입니다
(AbortIncompleteMultipartUpload, 7일). 이 규칙 하나가 버킷을 만들 때 기본으로
넣어야 할 설정 입니다.
현장에서 만나는 모습
중단된 멀티파트 업로드는 조용히 돈을 먹습니다. 완료되지 않은 업로드의 파트들은 저장 공간을 차지하지만 목록에는 객체로 보이지 않습니다. 클라이언트가 죽거나 배포 중에 끊긴 업로드가 몇 달 쌓이면 청구서에 이유 없는 수백 GB 가 나타납니다. AWS 에서 흔한 비용 누수 항목입니다.
대응은 둘입니다. 애플리케이션이 실패 시 명시적으로 중단(abort)하는 것, 그리고 버킷 수명주기 규칙에 AbortIncompleteMultipartUpload 를 7일 정도로 걸어 두는 것입니다. 후자를 기본으로 걸어 두는 것이 안전합니다.
다음 실습에서 할 것
24MiB 파일을 5MiB 파트로 나눠 직접 멀티파트 업로드합니다. UploadId 를 받고, 파트를 올리고, 완료하고, 그다음 멀티파트 ETag 를 손으로 계산해 서버 값과 맞춰 봅니다. 마지막에 업로드를 시작만 하고 중단하는 것까지 해 봅니다.