포스트

대규모 시스템 설계 기초 2 (9장)

S3 와 유사한 객체 저장소

저장소 시스템

저장소 시스템에는 3가지 있다.

  1. 블록 저장소 : HDD 나 SSD 처럼 서버에 물리적으로 연결되는 형태의 드라이브. raw block 을 서버에 volume 형태로 제공한다.
  2. 파일 저장소 : 파일과 디렉토리로 추상회된 데이터를 관리한다. 데이터는 계층적으로 구성되는 디렉터리 안에 보관된다.
  3. 객체 저장소 : 실시간으로 갱신할 필요가 없는 차가운(cold) 상태의 데이터 보관에 초점을 맞춘다. 계층적 디렉토리 구조를 제공하지 않으며, 다른 저장소에 비해 상대적으로 느리다.

용어 정리

  • 버킷 : 객체를 보관하는 논리적 컨테이너, 이름은 전역적으로 유일해야 한다.
  • 객체 : 버킷에 저장하는 개별 데이터로, 메타데이터를 갖는다.
  • 버전 : 한 객체의 여러 버전을 같은 버킷 안에 둘 수 있도록 하는 기능이다.

객체 저장소의 속성

  • 객체 불변성 : 객체 저장소와 다른 두 가지 유형의 저장소 시스템의 차이는 객체 저장소에 보관되는 객체들은 변경이 불가능하다는 점이다.
  • 키-값 저장소 : 객체 저장소를 사용하는 경우, 해당 객체의 URI 를 사용하여 데이터를 가져올 수 있다.
  • 저장은 1회, 읽기는 여러 번 : 데이터 접근 패턴 측면에서 보면 쓰기는 1회, 읽기는 여러번 발생한다.
  • 소형 및 대형 객체 동시 지원 : 다양한 크기의 객체를 문제 없이 저장할 수 있다.

객체 저장소의 설계 철학은 UNIX 파일 시스템의 설계 철학과 유사하다. 유닉스에서 실제 파일과, 파일의 저장 정보를 담고 있는 아이노드가 별개로 저장된다. 데이터를 읽기 위해서는 아이노드를 읽고, 파일 블록 포인터 목록을 확인해야 한다.
객체 저장소도 이처럼, 메타 데이터 저장소를 통해 데이터 식별자를 확인하고, 네트워크 요청을 통해서 데이터 저장소에서 ID 로 객체를 조회한다.

객체 업로드

객체는 버킷 안에 존재해야 한다.

  1. 클라이언트는 버킷을 생성하는 요청을 보낸다.
  2. API 서비스는 IAM 을 호출하여, 해당 사용자가 WRITE 권한을 가졌는지 확인한다.
  3. API 서비스는 메타데이터 db 에 버킷 정보를 등록한다.
  4. 버킷이 만들어지면, 클라이언트는 파일에 대한 PUT 요청을 보낸다.
  5. API 서비스는 IAM 을 호출하여, 해당 사용자의 권한을 확인한다.
  6. 문제가 없다면, API 서비스는 객체 데이터를 데이터 저장소로 보낸다. 저장소는 데이터를 객체로 저장하고, UUID 를 반환한다.
  7. API 서비스는 메타데이터 저장소를 호출하여 새로운 항목을 등록한다.

객체 다운로드

버킷 이름 + 객체 이름의 조합으로 폴더 구조와 유사한 논리적 계층을 만든다. 객체를 가져올 때는 GET 요청에 해당 이름을 넣으면 될 것이다.

  1. 클라이언트는 GET 요청을 서버로 보낸다.
  2. API 서버는 IAM 을 호출하여, 해당 사용자가 READ 권한을 가지고 있는지 확인한다.
  3. 권한이 있다면, API 서비스는 객체의 UUID 를 메타데이터 저장소에서 가져온다.
  4. API 서비스는 UUID 를 사용해 데이터 저장소에서 객체 데이터를 가져온다.
  5. API 서비스는 GET 요청에 대한 응답으로 객체 데이터를 반환한다.

데이터 저장소

3가지 컴포넌트로 구성된다.

데이텅 라우팅 서비스

데이터 노드 클러스터에 접근할 수 있는 stateless 서비스다.

  • 배치 서비스를 호출하여 데이터를 저장할 최적의 노드를 판단.
  • 노드에서 데이터를 읽어 API 서비스에 반환
  • 노드에 데이터를 기록

배치 서비스

어느 데이터 노드에 데이터를 저장할지 결정하는 역할을 한다. 내부의 클러스터 지도를 통해서 데이터 사본이 물리적으로 다른 위치에 놓이도록 한다.(내구성 달성을 위함)
배치 서비스는 데이터 노드와 펄스 통신을 하며 상태를 모니터링한다.

데이터 노드

데이터 노드는 실제 객체 데이터가 보관되는 곳이다. 여러 노드에 데이터를 복제함으로써 데이터의 안정성과 내구성을 보증한다.

데이터 저장 흐름

데이터가 데이터 노드에 보관되는 플로우다.

  1. API 서비스는 객체 데이터를 데이터 저장소로 포워딩한다.
  2. 데이터 라우팅 서비스는 해당 객체에 UUID 를 할당하고, 배치 서비스에 해당 객체를 보관할 데이터 노드를 질의한다.
  3. 배치 서비스가 반환한 데이터 노드로, 데이터와 UUID 가 전송된다.
  4. 주 데이터 노드는 데이터를 자기 노드에 저장하며, 부 노드에 다중화한다. 저장이 완료되면 데이터 라우팅 서비스에 응답을 보낸다.
  5. 객체의 UUID 를 API 서비스에 반환한다.

다중화를 통해서 데이터 일관성이 보장된다. 그러나 모든 노드의 다중화가 완료된 시점에 응답을 하게 되면 응답 지연이 발생한다. 주 데이터 및 부 데이터 노드 1개에만 다중화가 완료된 경우에 성공적으로 저장됐다고 간주하고 응답을 보내면 중간 정도의 응답 지연을 제공할 수 있을 것이다.

데이터 저장 시스템

가장 단순하게 저장하는 방법은 각각의 객체를 개별 파일로 저장하는 것이다. 이 경우, 작은 파일이 많아지면 낭비되는 데이터 블록이 발생한다. 또한, 시스템 아이노드 용량 한계를 초과하여 용량이 충분함에도 더이상 파일이 저장될 수 없는 경우가 발생할 수 있다.
이 문제는 작은 객체들을 큰 파일 하나로 모으는 방법을 통해 해결할 수 있다. 개념적으로는 WAL(Write-Ahead Log)와 같이 객체를 저장할 때 이미 존재하는 파일에 추가하는 방식이다.
용량 임계치에 도달한 파일은 읽기 전용 파일로 변경하고, 새로운 파일을 만든다. 이 때, 읽기-쓰기 파일에 대한 쓰기 연산은 순차적으로 이루어져야 한다. 여러 CPU 코어가 쓰기 연산을 병렬로 진행하더라도 객체 내용이 뒤섞이는 일은 없어야 한다.
이를 해결하기 위해서는, 코어별로 전담 읽기-쓰기 파일을 두어야 한다.

객체 소재 확인

하나의 거대한 데이터 파일 안에 작은 객체가 들어가 있다면 어떻게 UUID 로 객체 위치를 찾을 수 있을까. 다음 정보만 있으면 된다.

  • 객체가 보관된 데이터 파일
  • 데이터 파일 내 객체 오프셋
  • 객체 크기

객체의 UUID 를 키로 하면서, 이 데이터가 들어간 스키마를 구성하자.

데이터 내구성

데이터 내구성을 갖추기 위해서는 다중화를 해야 하한다.

하드웨어 장애와 장애 도메인

하드 디스크 장애는 피할 수 없다. 회전식 드라이브의 연간 장애율이 0.81 % 라면, 3중 복제만 하더라도 대략 0.999999 의 내구성을 달성할 수 있다.
데이터 센터의 가용성 구역(Availability Zone)을 구성함으로서, 데이터를 여러 가용성 구역에 복제해 놓으면 장애 여파를 최소화할 수 있다.

소거 코드

소거 코드는 데이터를 작은 단위로 분할하여 다른 서버에 배치하는 한편, 그 가운데 일부가 소실되었을 때 복구하기 위한 패리티 라는 정보를 만드는 방법이다. 장애가 발생하면 남은 데이터와 패리티를 조합하여 소실된 부분을 복구한다.

비교

항목다중화소거 코드
내구성99.9999%99.9999999999%(8+4 소거 코드를 사용하는 경우)
저장소 효율성200% 의 오버 헤드50% 의 오버헤드
계산 자원계산이 필요 없음패리티 계산에 자원 소모
쓰기 성능데이터를 여러 노드에 복제데이터를 디스크에 기록하기 전에 패리티 계산이 필요함
읽기 성능장애가 발생하지 않은 노드에서 데이터를 읽음데이터를 읽어야 할 때마다 클러스터 내의 여러 노드에서 데이터를 가져와야 함

정확성 검증

데이터 훼손 문제가 발생한 경우, 검증이 필요하다. 이는 프로세스 경계에 데이터 검증을 위한 체크섬을 두어 해결할 수 있다.
원본 데이터의 체크섬을 알면, 전송 받은 데이터의 체크섬을 다시 계산한 뒤에 둘을 비교함으로서 훼손 여부를 확인할 수 있다.
체크섬을 객체 데이터 끝에 두자. 파일을 읽기 전용으로 전환하기 전에 전체 파일의 체크섬을 계산한 후 파일 말미에 추가한다.

객체 버전

객체 버전은 버킷 안에 한 객체의 여러 버전을 둘 수 있도록 하는 기능이다. 메타 데이터 저장소의 객체 테이블에는 object_version 이라는 컬럼을 추가해야 할 것이다.
객체를 삭제하는 경우에는 객체의 모든 버전을 버킷 안에 그대로 둔 채, 단순히 삭제 표식만 추가한다.

쓰레기 수집

쓰레기 수집은 더 이상 사용되지 않는 데이터에 할당된 저장 공간을 자동으로 회수하는 절차다. 삭제된 객체는 정리 메커니즘을 주기적으로 실행하여 지운다.
쓰레기 수집기는 사용되지 않는 사본에 할당된 저장 공간을 회수하는 역할도 담당한다. 다음과 같은 메커니즘으로 작동한다.

  1. 쓰레기 수집기는 객체를 복사하되, 삭제 플래그가 있는 객체는 복사하지 않는다.
  2. 모든 객체가 복사되면 object_mapping 테이블을 갱신한다. file_name 과 start_offset 값을 변경하여 새 위치를 가리키도록 한다.
  3. 이전 객체를 삭제한다.
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.