S3와 유사한 객체 저장소
- 9.1 문제 이해 및 설계 범위 확정
- 9.2 개략적 설계안 제시
- 9.3 상세 설계
- 9.4 마무리
S3와 유사한 객체 저장소는 이미지, 동영상, 백업 파일, 로그 등 비정형 데이터를 대규모로 저장하고 조회하는 시스템이야. 파일 시스템이나 블록 스토리지와는 근본적으로 다른 설계 철학이 필요하지. 핵심은 데이터 내구성, 메타데이터 관리, 그리고 erasure coding이야.
객체 저장소의 핵심 특성부터 짚어보자.
- 객체(object) 단위로 데이터를 관리해. 파일 시스템처럼 디렉터리 구조가 아니지
- 객체는 버킷(bucket) 안에 저장돼. 버킷은 논리적 컨테이너야
- 각 객체는 **키(key)**로 식별돼. 보통 경로처럼 생긴 문자열 (예:
images/photo.jpg) - 수정(update)이 아니라 덮어쓰기(overwrite). 객체의 일부만 수정하는 건 안 돼
- WORM(Write Once Read Many) 패턴 — 한 번 쓰고 여러 번 읽는 게 일반적이지
설계 범위는:
- 객체의 업로드, 다운로드, 삭제
- 버저닝(versioning) — 같은 키로 여러 버전을 관리
- 대용량 파일 업로드 — 수 GB 파일 지원
- 99.9999%의 데이터 내구성 — 한번 저장된 데이터는 절대 유실되면 안 돼
객체 저장소는 크게 두 부분으로 나뉘어.
메타데이터 저장소 — 객체의 이름, 크기, 생성일, 버전 등의 정보를 관리해. 이건 RDBMS나 분산 KV 스토어로 처리하지.
데이터 저장소 — 객체의 실제 바이트 데이터를 저장해. 이건 분산 파일 시스템이나 raw 디스크에 직접 저장하지.
이 분리가 중요한 이유 — 메타데이터와 데이터의 접근 패턴이 완전히 다르기 때문이야. 메타데이터는 작고, 랜덤 접근이 많고, 일관성이 중요해. 데이터는 크고, 순차 접근이 많고, 내구성이 중요하지.
업로드 흐름:
- 클라이언트가 API 서버에 업로드 요청
- API 서버가 메타데이터를 저장소에 기록
- 실제 데이터를 데이터 저장소에 전송
- 데이터 저장 완료 후 메타데이터의 상태를 "업로드 완료"로 갱신
다운로드 흐름:
- 클라이언트가 버킷명 + 객체 키로 다운로드 요청
- API 서버가 메타데이터 저장소에서 해당 객체의 데이터 위치를 조회
- 데이터 저장소에서 실제 데이터를 읽어서 반환
객체를 파일 시스템의 파일 하나로 저장하면 안 되냐? 작은 규모에서는 되지만, 수십억 개의 객체가 있으면 inode가 부족해지거든. 파일 시스템은 파일마다 inode를 하나씩 소비하는데, 디스크의 inode 수에는 한계가 있어.
해결책은 여러 작은 객체를 하나의 큰 파일에 묶어서 저장하는 거야. 이 큰 파일을 WAL(Write-Ahead Log)처럼 순차적으로 기록하지. 각 객체가 파일 내 어느 위치(오프셋)에 있는지를 메타데이터로 관리해.
이 방식의 장점은:
- inode 소비가 대폭 줄어들어
- 순차 쓰기라서 디스크 I/O가 빨라
- 작은 객체의 랜덤 I/O를 방지하지
99.9999% 내구성은 어떻게 달성하는가? 가장 단순한 방법은 복제(replication) — 데이터를 3개 복사본으로 저장하면 하나가 죽어도 괜찮아. 하지만 3배의 저장 공간이 필요하지.
Erasure coding은 복제보다 저장 효율이 높으면서 동일한 내구성을 제공하는 기법이야.
원리는 이래. 데이터를 k개의 데이터 청크로 쪼개고, 여기서 p개의 패리티 청크를 수학적으로 계산해서 만들어. 총 k+p개의 청크 중 어떤 p개가 유실되어도 나머지 k개로 원본 데이터를 복구할 수 있지.
예를 들어 8+4 erasure coding이면, 데이터를 8개 청크로 쪼개고 4개의 패리티를 만들어. 총 12개 청크를 12개의 서로 다른 노드에 분산 저장하면, 아무 4개 노드가 죽어도 나머지 8개에서 복구 가능해.
저장 오버헤드는 12/8 = 1.5배. 3-복제(3배)보다 훨씬 효율적이면서 내구성은 비슷하거나 더 높지. S3가 실제로 erasure coding을 사용해.
단점은 읽기/쓰기 시 인코딩/디코딩 계산 비용이 든다는 거야. 하지만 현대 하드웨어에서는 충분히 감당 가능한 수준이지.
메타데이터는 버킷 정보, 객체 정보, 객체-데이터 위치 매핑 세 가지를 관리해.
접근 패턴에 따라 적합한 DB가 달라.
- 버킷 정보 — 버킷 수가 상대적으로 적으니 RDBMS로 충분
- 객체 메타데이터 — 수십억 건이니 분산 KV 스토어(DynamoDB, Cassandra 등)가 적합
- 샤딩 키는 객체 ID — 같은 객체의 메타데이터 조회가 빠르도록
같은 키로 새 객체를 업로드하면 이전 버전을 삭제하지 않고 버전 ID를 부여해서 유지해. 최신 버전이 기본으로 반환되지만, 특정 버전을 지정해서 가져올 수도 있지.
삭제도 마찬가지 — 실제로 데이터를 지우는 게 아니라 **삭제 마커(delete marker)**를 추가해. 버전을 지정하면 삭제된 객체도 복구 가능하지.
수 GB 파일을 한 번에 올리면 네트워크가 끊겼을 때 처음부터 다시 올려야 해. 멀티파트 업로드는 파일을 여러 조각(part)으로 나눠서 병렬로 올리고, 서버에서 합치는 방식이야.
- 클라이언트가 멀티파트 업로드 시작을 요청 → 업로드 ID 발급
- 파일을 파트로 나눠서 각각 업로드 (병렬 가능, 실패 시 해당 파트만 재시도)
- 모든 파트 업로드 완료 후 "완료" 요청 → 서버가 파트들을 합쳐서 하나의 객체로 저장
삭제 마커가 붙은 객체, 미완료 멀티파트 업로드의 파트, 고아 데이터(메타데이터는 삭제됐는데 데이터는 남아있는 경우) 등을 주기적으로 정리하는 가비지 컬렉터가 필요하지. 백그라운드 프로세스로 동작해.
객체 저장소는 현대 클라우드 인프라의 기반이야. 모든 서비스가 어떤 형태로든 S3 같은 저장소에 의존하지. 특히 erasure coding은 단순 복제를 대체하는 핵심 기술이니 개념을 확실히 이해해 두는 게 좋아.
정리
9장 읽고 기억할 거 세 가지:
- 객체 저장소는 메타데이터와 데이터를 분리하고, 접근 패턴에 맞는 별도의 저장소를 사용해. 메타데이터는 분산 KV 스토어, 데이터는 디스크에 순차 기록하지.
- Erasure coding은 3-복제보다 저장 효율이 높으면서 동등한 내구성을 제공해. k+p 청크 중 p개가 유실돼도 원본을 복구할 수 있지.
- 대용량 파일은 멀티파트 업로드로 처리하고, 삭제는 삭제 마커를 통해 버저닝과 복구를 지원해. 실제 데이터 정리는 가비지 컬렉터가 백그라운드에서 수행하지.