Design Object Storage System
저장소 시스템의 부류
Section titled “저장소 시스템의 부류”저장소 시스템은 크게 세 가지 부류로 나눌 수 있다.
- 블록 저장소: 원시 블록을 서버에 볼륨 형태로 제공하는 시스템
- 파일 저장소: 블록 저장소 위에 구현되며, 파일과 디렉터리를 손쉽게 다룰 수 있도록 추상화한 시스템
- 객체 저장소: 파일 저장소 위에 구현되며, 파일을 객체로 추상화한 시스템
- 데이터 영속성을 높이고 대규모 애플리케이션을 지원
- 비용을 낮추기 위해 성능을 희생
- 모든 데이터를 수평적 구조로 저장(계층적 디렉토리 구조 X)
객체 저장소에서의 용어
Section titled “객체 저장소에서의 용어”- 버킷(bucket): 객체를 저장하는 논리적 컨테이너로, 이름은 전역적으로 유일해야 함
- 객체(object): 버킷에 저장하는 데이터와 메타데이터로 구성된 개별 데이터
- 버전(versioning): 한 객체의 여러 버전을 같은 버킷 안에 둘 수 있도록 하는 버전 관리 기능
- URI(Uniform Resource Identifier): 객체를 식별하는 고유한 주소
- SLA(Service-Level Agreement): 서비스 제공자와 사용자 간의 서비스 수준을 명시한 계약
객체 저장소 특징
Section titled “객체 저장소 특징”- 객체 불변성: 다른 저장소와 달리, 저장되는 객체들은 변경이 불가능함
- 키-값 저장소: 객체는 키와 값으로 구성됨(key: URI, value: 객체 데이터)
- 저장 1회 / 읽기 여러 회: 데이터 접근 패턴 측면에서 쓰기는 1회, 읽기는 여러 회로 구성됨
- 소형 및 대형 객체 동시 지원: 다양한 크기의 객체 저장 가능
- 기능
- 버킷 생성
- 객체 업로드 및 다운로드
- 객체 버전
- 버킷 내 객체 목록 출력 기능
- 규모
- 객체 크기: 소형 객체부터 대형 객체(수 GB 이상)
- 1MB 미만 객체(중앙값 0.5MB): 20%
- 1MB ~ 64MB 객체(중앙값: 32MB): 60%
- 64MB 초과 객체(중앙값: 200MB): 20%
- 매년 추가 데이터: 100PB
- 내구성 및 가용성: 99.999%
- 저장 공간 사용률: 40%
- 메타데이터 크기: 1KB
- 객체 크기: 소형 객체부터 대형 객체(수 GB 이상)
- 추정
- 객체 수 = 100PB * 40% (20% * 0.5MB + 60% * 32MB + 20% * 200MB) = 6억 8천만 개
- 메타데이터 공간 = 6억 8천만 개 * 1KB = 0.68TB
객체 저장소 서비스를 구축하기 위해 필요한 컴포넌트들은 다음과 같다.
[로드밸런서] [데이터 저장소 서비스(secondary)] | |[IAM] ---------- [API 서비스] ------------ [데이터 저장소 서비스(primary)] | | [메타데이터 서비스] [데이터 저장소 서비스(secondary)] | [메타데이터 DB]- 로드밸런서: RESTful API 요청 분산
- API 서비스: IAM 서비스 / 메타데이터 서비스 / 저장소 서비스에 대한 호출 조율 역할
- IAM: 인증 / 권한 부여 / 접근 제어 등을 중앙에서 맡아 처리
- 데이터 저장소: 실제 데이터 보관, 관련 연산은 고유 ID(UUID)를 이용하여 처리
- 메타데이터 저장소: 객체 메타데이터 보관
객체 저장소 기능의 흐름
Section titled “객체 저장소 기능의 흐름”객체 저장소는 크게 업로드 / 다운로드 / 객체 목록 조회 세 가지의 기능을 제공하며, 그 흐름은 다음과 같다.
객체 업로드
Section titled “객체 업로드”객체는 버킷 안에 두어야 하기 때문에, 버킷을 만들고 해당 버킷에 파일을 업로드하는 과정은 아래와 같이 진행된다.
- 버킷 생성
- 클라이언트에서 버킷 생성 API 요청
- API 서비스에서 IAM 서비스를 호출해 권한 확인
- 버킷 정보를 등록하기 위해 메타데이터 저장소를 호출하여 버킷 생성
- 객체 업로드
- 클라이언트에서 객체 업로드 API 요청
- API 서비스에서 IAM 서비스를 호출해 권한 확인
- 객체 데이터를 등록하기 위해 데이터 저장소 서비스를 호출하여 객체 업로드 후 UUID 반환 받음
- 반환 받은 UUID / 버킷 정보 / 객체 데이터를 메타데이터 저장소에 저장
객체 다운로드
Section titled “객체 다운로드”버킷은 디렉터리 같은 계층 구조를 지원하지 않지만, 버킷 이름과 객체 이름을 연결해 논리적 계층을 구성할 수 있다.(예: bucket_name/object_name)
클라이언트가 객체를 요청할 때, 연결한 버킷 이름과 객체 이름을 이용해 객체를 조회할 수 있다.
- 클라이언트에서
/bucket_name/object_name으로 API 요청 - API 서비스에서 IAM 서비스를 호출해 권한 확인
- 메타데이터 저장소에서 버킷 정보와 객체 데이터를 조회
- 조회된 데이터에서 해당 객체 UUID로 데이터 저장소 서비스를 호출하여 객체 데이터를 반환
- 반환된 객체 데이터를 클라이언트에 전달
데이터 저장소 설계
Section titled “데이터 저장소 설계”데이터 저장소는 내부적으로 크게 세 가지 주요 컴포넌트로 구성된다.
----- 데이터 트래픽 전송 --------------------------------- | | | ------박동 메시지------ [데이터 노드(primary)] | | | | | | 데이터 다중화 | | |[데이터 라우팅 서비스] ---------- [배치 서비스] -- 박동 메시지 -- [데이터 노드(secondary)] | | | | 데이터 다중화 | | ------박동 메시지------ [데이터 노드(secondary)]- 데이터 라우팅 서비스: 데이터 노드 클러스터에 접근하기 위한 서비스를 제공하며, 무상태 서비스로 구성
- 배치 서비스를 호출하여 데이터를 저장할 최적의 데이터 노드 판단
- 데이터 노드에서 데이터를 읽어 API 서비스에 반환
- 데이터 노드에 데이터 기록
- 배치 서비스: 어느 데이터 노드에 데이터를 저장할지 결정
- 데이터 노드의 위치 정보를 이용하여 데이터 사본이 물리적으로 다른 위치에 놓이도록 해 데이터 내구성을 높임
- 지속적으로 모든 데이터 노드와 박동 멩시지를 주고받아 데이터 노드의 상태를 확인
- 데이터 노드: 실제 데이터가 보관되는 곳
- 여러 노드에 데이터를 복제하여 안정성과 내구성 보증
- 배치 서비스에 주기적으로 저장된 데이터 양에 대한 정보를 포함한 박동 메시지 전송
데이터 저장 흐름
Section titled “데이터 저장 흐름”- 객체 데이터 저장 요청
- 데이터 라우팅 서비스에서 해당 객체에 UUID 할당 후 배치 서비스에 보관할 데이터 노드 질의
- 저장할 데이터 노드 선정 후 주 데이터 노드에 데이터 저장
- 주 데이터 노드는 부 데이터 노드에 다중화
- 저장 완료 후 객체 UUID 반환
객체 조회 방법
Section titled “객체 조회 방법”데이터를 저장하고 난 뒤 UUID로 객체를 조회하게 되는데, 해당 값으로 찾기 위해 매핑 테이블을 이용한다.
- 매핑 테이블: 객체 UUID와 데이터 노드 주소를 매핑한 테이블
object_id: 객체 UUIDfile_name: 객체가 보관된 데이터 파일start_offset: 데이터 파일 내 객체 오프셋object_size: 객체 크기
객체 저장소 특성상, 객체를 한 번 저장하면 변경되지 않으며 읽기 연산이 훨씬 더 빈번하게 발생하므로, RDBMS를 통해 매핑 테이블을 구성해볼 수 있다.
메타데이터 데이터 모델
Section titled “메타데이터 데이터 모델”메타데이터 데이터 모델을 통해서 다음 세 가지 질의를 지원할 수 있어야 한다.
- 객체 이름(
bucket_name/object_name)으로 객체 UUID 조회 - 객체 이름(
bucket_name/object_name)에 기반하여 객체 삽입 및 삭제 - 같은 접두어를 갖는 버킷 내의 모든 객체 조회
위 질의를 지원하기 위한 스키마는 다음과 같이 구성할 수 있다.
- bucket
bucket_name: 버킷 이름bucket_id: 버킷 UUIDowner_id: 버킷 소유자 IDenable_versioning: 버전 관리 활성화 여부
- object
bucket_name: 버킷 이름object_name: 객체 이름object_version: 객체 버전object_id: 객체 UUID
많은 양의 객체를 저장하는 만큼, 메타데이터도 많은 양을 저장하기 때문에 한대에 보관하기에는 한계가 있어 샤딩을 통해 확장할 수 있다.
- bucket 테이블: 이용자당 버킷의 갯수를 제한하여 샤딩 필요 없이 사본을 만들어 확장 가능
- object 테이블: 각 id 값을 기준으로 샤딩하여 확장 가능
bucket_id기준 샤딩: 하나의 버킷 안에 매우 많은 버킷이 들어갈 수 있기 때문에 적절하지 않음object_id기준 샤딩:bucket_name/object_name을 통해 객체를 조회하기 때문에 부적절bucket_name+object_name기준 샤딩: 대부분의 연산이 객체 URI를 통해 이루어지기 때문에 적절
객체 버전은 객체의 여러 버전을 저장할 수 있도록 하는 기능으로, 이전 데이터를 쉽게 복구할 수 있도록 해준다.
객체 저장소에서는 다음과 같은 방법을 사용하여 버전 기능을 제공할 수 있다.
- 클라이언트에서 객체 업로드 요청(이미 존재하는
bucket_name/object_name요청) - 신원 확인 후 데이터 저장소에 업로드 후 UUID를 반환 받음
- 반환 받은 UUID를 통해 메타 데이터 저장소에 저장
- 기존 레코드를 덮어쓰는 것이 아닌 같은
bucket_id/object_name으로 새로운 레코드 생성 - 새로운
object_version을 부여하여 레코드 추가(TIMEUUID같은 값으로 버전 관리)
- 기존 레코드를 덮어쓰는 것이 아닌 같은
큰 파일 업로드 최적화
Section titled “큰 파일 업로드 최적화”큰 파일을 업로드는 오랜 시간이 소요되며, 중간에 오류가 발생하면 다시 처음부터 업로드해야 하는 문제가 있기 때문에 멀티파트 업로드를 통해 해결하고 있다.
- 클라이언트에서 업로드를 하기 위해 데이터 저장소 호출
- 데이터 저장소가
uploadID반환(유일 식별 ID) - 클라이언트는 파일을 작은 객체로 분할한 뒤에 업로드 시작
- 분할된 조각 하나가 업로드 될 때마다 데이터 저장소에서는
ETag반환(해당 조각에 대한 MD5 해시 체크섬) - 모든 분할된 조각을 업로드한 클라이언트는 멀티파트 업로드 종료 요청
- 해당 요청에
uploadID, 조각 번호 목록,ETag목록을 함께 보냄
- 해당 요청에
- 데이터 저장소는 조각 번호 목록을 사용해 원본 객체로 복원 후 성공 메시지 반환
Garbage Collection
Section titled “Garbage Collection”더 이상 사용되지 않는 데이터에 할당된 저장된 공간을 회수하는 절차로, 다음과 같은 경우에 쓰레기 데이터가 생길 수 있다.
- 객체 지연 삭제(lazy object deletion): 삭제 시 바로 삭제하는 것이 아닌, 표시만 해둔 뒤 일정 시간이 지나면 삭제
- 갈 곳 없는 데이터(orphaned data): 반쯤 업로드된 데이터 / 취소된 멀티파트 업로드 데아터
- 훼손된 데이터(corrupted data): 체크섬 검사에 실패한 데이터