Design Object Storage System

저장소 시스템의 부류

저장소 시스템은 크게 세 가지 부류로 나눌 수 있다.

  • 블록 저장소: 원시 블록을 서버에 볼륨 형태로 제공하는 시스템

  • 파일 저장소: 블록 저장소 위에 구현되며, 파일과 디렉터리를 손쉽게 다룰 수 있도록 추상화한 시스템

  • 객체 저장소: 파일 저장소 위에 구현되며, 파일을 객체로 추상화한 시스템

    • 데이터 영속성을 높이고 대규모 애플리케이션을 지원

    • 비용을 낮추기 위해 성능을 희생

    • 모든 데이터를 수평적 구조로 저장(계층적 디렉토리 구조 X)

객체 저장소에서의 용어

  • 버킷(bucket): 객체를 저장하는 논리적 컨테이너로, 이름은 전역적으로 유일해야 함

  • 객체(object): 버킷에 저장하는 데이터와 메타데이터로 구성된 개별 데이터

  • 버전(versioning): 한 객체의 여러 버전을 같은 버킷 안에 둘 수 있도록 하는 버전 관리 기능

  • URI(Uniform Resource Identifier): 객체를 식별하는 고유한 주소

  • SLA(Service-Level Agreement): 서비스 제공자와 사용자 간의 서비스 수준을 명시한 계약

객체 저장소 특징

  • 객체 불변성: 다른 저장소와 달리, 저장되는 객체들은 변경이 불가능함

  • 키-값 저장소: 객체는 키와 값으로 구성됨(key: URI, value: 객체 데이터)

  • 저장 1회 / 읽기 여러 회: 데이터 접근 패턴 측면에서 쓰기는 1회, 읽기는 여러 회로 구성됨

  • 소형 및 대형 객체 동시 지원: 다양한 크기의 객체 저장 가능

요구사항

  • 기능

    • 버킷 생성

    • 객체 업로드 및 다운로드

    • 객체 버전

    • 버킷 내 객체 목록 출력 기능

  • 규모

    • 객체 크기: 소형 객체부터 대형 객체(수 GB 이상)

      • 1MB 미만 객체(중앙값 0.5MB): 20%

      • 1MB ~ 64MB 객체(중앙값: 32MB): 60%

      • 64MB 초과 객체(중앙값: 200MB): 20%

    • 매년 추가 데이터: 100PB

    • 내구성 및 가용성: 99.999%

    • 저장 공간 사용률: 40%

    • 메타데이터 크기: 1KB

  • 추정

    • 객체 수 = 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)를 이용하여 처리

  • 메타데이터 저장소: 객체 메타데이터 보관

객체 저장소 기능의 흐름

객체 저장소는 크게 업로드 / 다운로드 / 객체 목록 조회 세 가지의 기능을 제공하며, 그 흐름은 다음과 같다.

객체 업로드

객체는 버킷 안에 두어야 하기 때문에, 버킷을 만들고 해당 버킷에 파일을 업로드하는 과정은 아래와 같이 진행된다.

  • 버킷 생성

    1. 클라이언트에서 버킷 생성 API 요청

    2. API 서비스에서 IAM 서비스를 호출해 권한 확인

    3. 버킷 정보를 등록하기 위해 메타데이터 저장소를 호출하여 버킷 생성

  • 객체 업로드

    1. 클라이언트에서 객체 업로드 API 요청

    2. API 서비스에서 IAM 서비스를 호출해 권한 확인

    3. 객체 데이터를 등록하기 위해 데이터 저장소 서비스를 호출하여 객체 업로드 후 UUID 반환 받음

    4. 반환 받은 UUID / 버킷 정보 / 객체 데이터를 메타데이터 저장소에 저장

객체 다운로드

버킷은 디렉터리 같은 계층 구조를 지원하지 않지만, 버킷 이름과 객체 이름을 연결해 논리적 계층을 구성할 수 있다.(예: bucket_name/object_name) 클라이언트가 객체를 요청할 때, 연결한 버킷 이름과 객체 이름을 이용해 객체를 조회할 수 있다.

  1. 클라이언트에서 /bucket_name/object_name으로 API 요청

  2. API 서비스에서 IAM 서비스를 호출해 권한 확인

  3. 메타데이터 저장소에서 버킷 정보와 객체 데이터를 조회

  4. 조회된 데이터에서 해당 객체 UUID로 데이터 저장소 서비스를 호출하여 객체 데이터를 반환

  5. 반환된 객체 데이터를 클라이언트에 전달

데이터 저장소 설계

데이터 저장소는 내부적으로 크게 세 가지 주요 컴포넌트로 구성된다.

        ----- 데이터 트래픽 전송 ---------------------------------
       |                                                      |
       |                           ------박동 메시지------ [데이터 노드(primary)]
       |                          |                           | 
       |                          |                           | 데이터 다중화
       |                          |                           | 
[데이터 라우팅 서비스]  ---------- [배치 서비스] -- 박동 메시지 -- [데이터 노드(secondary)]
                                  |                           |
                                  |                           | 데이터 다중화
                                  |                           |
                                   ------박동 메시지------ [데이터 노드(secondary)]
  • 데이터 라우팅 서비스: 데이터 노드 클러스터에 접근하기 위한 서비스를 제공하며, 무상태 서비스로 구성

    • 배치 서비스를 호출하여 데이터를 저장할 최적의 데이터 노드 판단

    • 데이터 노드에서 데이터를 읽어 API 서비스에 반환

    • 데이터 노드에 데이터 기록

  • 배치 서비스: 어느 데이터 노드에 데이터를 저장할지 결정

    • 데이터 노드의 위치 정보를 이용하여 데이터 사본이 물리적으로 다른 위치에 놓이도록 해 데이터 내구성을 높임

    • 지속적으로 모든 데이터 노드와 박동 멩시지를 주고받아 데이터 노드의 상태를 확인

  • 데이터 노드: 실제 데이터가 보관되는 곳

    • 여러 노드에 데이터를 복제하여 안정성과 내구성 보증

    • 배치 서비스에 주기적으로 저장된 데이터 양에 대한 정보를 포함한 박동 메시지 전송

데이터 저장 흐름

  1. 객체 데이터 저장 요청

  2. 데이터 라우팅 서비스에서 해당 객체에 UUID 할당 후 배치 서비스에 보관할 데이터 노드 질의

  3. 저장할 데이터 노드 선정 후 주 데이터 노드에 데이터 저장

  4. 주 데이터 노드는 부 데이터 노드에 다중화

  5. 저장 완료 후 객체 UUID 반환

객체 조회 방법

데이터를 저장하고 난 뒤 UUID로 객체를 조회하게 되는데, 해당 값으로 찾기 위해 매핑 테이블을 이용한다.

  • 매핑 테이블: 객체 UUID와 데이터 노드 주소를 매핑한 테이블

    • object_id: 객체 UUID

    • file_name: 객체가 보관된 데이터 파일

    • start_offset: 데이터 파일 내 객체 오프셋

    • object_size: 객체 크기

객체 저장소 특성상, 객체를 한 번 저장하면 변경되지 않으며 읽기 연산이 훨씬 더 빈번하게 발생하므로, RDBMS를 통해 매핑 테이블을 구성해볼 수 있다.

메타데이터 데이터 모델

메타데이터 데이터 모델을 통해서 다음 세 가지 질의를 지원할 수 있어야 한다.

  1. 객체 이름(bucket_name/object_name)으로 객체 UUID 조회

  2. 객체 이름(bucket_name/object_name)에 기반하여 객체 삽입 및 삭제

  3. 같은 접두어를 갖는 버킷 내의 모든 객체 조회

위 질의를 지원하기 위한 스키마는 다음과 같이 구성할 수 있다.

  • bucket

    • bucket_name: 버킷 이름

    • bucket_id: 버킷 UUID

    • owner_id: 버킷 소유자 ID

    • enable_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를 통해 이루어지기 때문에 적절

객체 버전

객체 버전은 객체의 여러 버전을 저장할 수 있도록 하는 기능으로, 이전 데이터를 쉽게 복구할 수 있도록 해준다. 객체 저장소에서는 다음과 같은 방법을 사용하여 버전 기능을 제공할 수 있다.

  1. 클라이언트에서 객체 업로드 요청(이미 존재하는 bucket_name/object_name 요청)

  2. 신원 확인 후 데이터 저장소에 업로드 후 UUID를 반환 받음

  3. 반환 받은 UUID를 통해 메타 데이터 저장소에 저장

    • 기존 레코드를 덮어쓰는 것이 아닌 같은 bucket_id / object_name으로 새로운 레코드 생성

    • 새로운 object_version을 부여하여 레코드 추가(TIMEUUID같은 값으로 버전 관리)

큰 파일 업로드 최적화

큰 파일을 업로드는 오랜 시간이 소요되며, 중간에 오류가 발생하면 다시 처음부터 업로드해야 하는 문제가 있기 때문에 멀티파트 업로드를 통해 해결하고 있다.

  1. 클라이언트에서 업로드를 하기 위해 데이터 저장소 호출

  2. 데이터 저장소가 uploadID 반환(유일 식별 ID)

  3. 클라이언트는 파일을 작은 객체로 분할한 뒤에 업로드 시작

  4. 분할된 조각 하나가 업로드 될 때마다 데이터 저장소에서는 ETag 반환(해당 조각에 대한 MD5 해시 체크섬)

  5. 모든 분할된 조각을 업로드한 클라이언트는 멀티파트 업로드 종료 요청

    • 해당 요청에 uploadID, 조각 번호 목록, ETag 목록을 함께 보냄

  6. 데이터 저장소는 조각 번호 목록을 사용해 원본 객체로 복원 후 성공 메시지 반환

Garbage Collection

더 이상 사용되지 않는 데이터에 할당된 저장된 공간을 회수하는 절차로, 다음과 같은 경우에 쓰레기 데이터가 생길 수 있다.

  • 객체 지연 삭제(lazy object deletion): 삭제 시 바로 삭제하는 것이 아닌, 표시만 해둔 뒤 일정 시간이 지나면 삭제

  • 갈 곳 없는 데이터(orphaned data): 반쯤 업로드된 데이터 / 취소된 멀티파트 업로드 데아터

  • 훼손된 데이터(corrupted data): 체크섬 검사에 실패한 데이터

참고자료

Last updated

Was this helpful?