테마 선택

📚 지식 노트

AI·하드웨어·주변기기·게임·컴퓨터 시스템·이론 컴퓨터 과학·수학·개발과 운영·보안·응용 분야에 대해 알아 두면 좋은 내용을 정리한 노트다. 위에서 분야를 고르고 아래에서 노트를 고른다.

클라우드 데이터베이스의 내부

Aurora, DynamoDB, Spanner, BigQuery처럼 클라우드가 직접 만든 데이터베이스가 저장과 계산, 복제를 다시 설계한 방식이다.

목차 (7)
  1. 왜 클라우드 데이터베이스는 다르게 설계하나
  2. Amazon Aurora: 로그가 곧 데이터베이스다
  3. DynamoDB: 파티션마다 리더를 둔 키-값 저장소
  4. Google Spanner: 시계를 다시 만들어 전 세계 트랜잭션을 하다
  5. BigQuery와 Snowflake: 오브젝트 스토리지 위의 분석
  6. 다른 갈래: 분산 SQL 오픈 소스, Neon, 관리형 오픈 소스
  7. 고르는 법

왜 클라우드 데이터베이스는 다르게 설계하나

PostgreSQL이나 MySQL 같은 전통적인 데이터베이스는 서버 한 대를 전제로 자랐다. SQL을 처리하는 프로세스와 버퍼 풀, 그리고 데이터 파일이 놓인 디스크가 한 기계 안에 있다. 이 구조는 저장 엔진 노트와 관계형 엔진 노트에 있다. 이런 데이터베이스를 그대로 클라우드 가상 머신에 올려도 잘 돈다. 그런데 클라우드 회사가 수십만 고객의 데이터베이스를 직접 운영하게 되자, 한 대를 전제로 한 설계가 세 군데에서 걸리기 시작했다.

  • 계산과 저장이 묶여 있다: 읽기를 늘리려고 복제 서버를 하나 더 두면 데이터 전체를 새 디스크에 복사해야 한다. 디스크가 가득 차면 계산은 남아도 더 큰 서버로 옮겨야 한다. 둘은 늘어나는 속도가 다른데 한 덩어리로만 늘릴 수 있다.
  • 병목이 디스크에서 네트워크로 옮겨 갔다: 클라우드의 디스크는 대개 네트워크 너머에 있는 블록 스토리지다(네트워크 스토리지 노트). 페이지 하나를 고칠 때마다 로그, 데이터 페이지, 복제본 몫까지 여러 번 네트워크를 건너면, 디스크가 아무리 빨라도 네트워크가 먼저 막힌다.
  • 장애가 늘 일어난다: 서버 수십만 대 규모에서는 디스크, 노드, 랙, 심지어 데이터센터(가용 영역, AZ) 하나가 멈추는 일이 예외가 아니라 일상이다. 사람이 손으로 장애 조치를 하는 설계로는 버틸 수 없다.
계산과 저장을 합친 구성과 나눈 구성: 왼쪽은 서버마다 SQL 처리와 로컬 디스크를 함께 두어 복제하려면 데이터를 통째로 복사한다. 오른쪽은 쓰기 노드와 읽기 노드가 네트워크 너머 공유 분산 저장 계층을 함께 쓴다합친 구성: 서버마다 자기 디스크SQL·버퍼 풀로컬 디스크주 서버SQL·버퍼 풀로컬 디스크복제 서버복사늘리려면 데이터 전체를 새 디스크에 복사나눈 구성: 계산과 저장이 따로쓰기 노드읽기 노드읽기 노드네트워크공유 분산 저장 계층계산은 따로 늘리고, 저장은 여러 AZ에 복제
왼쪽은 서버마다 SQL 처리와 디스크를 함께 두는 구성이라, 늘리려면 데이터를 통째로 복사한다. 오른쪽은 계산 노드 여럿이 네트워크 너머 공유 저장 계층을 함께 쓴다. 계산은 몇 초 만에 늘리고 줄이며, 저장은 여러 AZ에 복제된 채 따로 커진다.

그래서 클라우드가 직접 만든 데이터베이스는 대부분 저장과 계산을 나눈다(disaggregation). 계산 쪽은 상태를 거의 들지 않아 쉽게 바꾸고 늘리며, 저장 쪽은 여러 AZ에 복제된 분산 시스템이 맡는다. 이 노트의 서비스들은 같은 생각을 서로 다른 자리에 적용했다. Aurora는 관계형 데이터베이스의 저장 엔진을 뜯어냈고, DynamoDB는 키-값 저장소를 파티션 단위로 쪼갰으며, Spanner는 시계를 다시 만들어 전 세계에 걸친 트랜잭션을 했고, BigQuery와 Snowflake는 분석용 열 저장을 오브젝트 스토리지 위에 올렸다. 복제·샤딩·합의의 기본은 복제·샤딩·합의 노트에 있으니, 여기서는 각 서비스가 그것을 어떻게 다시 조합했는지에 집중한다.

Amazon Aurora: 로그가 곧 데이터베이스다

Aurora는 AWS가 MySQL(뒤에 PostgreSQL도)과 호환되게 만든 관계형 데이터베이스다. 설계는 SIGMOD 2017 논문 Amazon Aurora: Design Considerations for High Throughput Cloud-Native Relational Databases에 있다. 논문의 출발점은 처리량이 큰 데이터베이스의 병목이 이제 계산이나 저장 장치가 아니라 둘 사이의 네트워크로 옮겨 갔다는 관찰이다.

MySQL을 네트워크 블록 스토리지에 올리고 다른 AZ에 미러링하면, 트랜잭션 하나가 redo 로그, binlog, 고친 데이터 페이지, 페이지가 반쯤 써지는 것을 막는 더블 라이트, 메타데이터 파일을 각각 쓰고, 그것이 미러로 또 한 번 건너간다. 그것도 상당수가 차례로 기다려야 하는 쓰기다. Aurora는 여기서 네트워크를 건너는 쓰기를 redo 로그 레코드 하나로 줄였다. 데이터베이스 인스턴스는 페이지를 저장소에 쓰지 않는다. "이 페이지의 이 자리를 이렇게 바꿨다"는 로그만 보내고, 그 로그를 페이지로 합치는 일(materialization)은 저장 계층이 맡는다. 논문이 "로그가 곧 데이터베이스"(the log is the database)라고 부르는 생각이다. WAL과 redo 로그 자체는 저장 엔진 노트에 있다.

저장 계층은 데이터베이스 볼륨을 10GB 조각(segment)으로 나누고, 조각마다 사본 여섯을 AZ 세 곳에 둘씩 둔다. 이 여섯 사본이 보호 그룹(protection group)이다. 쓰기는 여섯 중 넷, 읽기는 셋이 답하면 되는 쿼럼을 쓴다. 4 + 3 > 6이라 어떤 읽기 쿼럼도 마지막 쓰기 쿼럼과 적어도 한 사본을 공유하고, 4 + 4 > 6이라 두 쓰기가 서로 모르고 지나갈 수 없다.

Aurora 쿼럼: 가용 영역 세 곳에 사본을 둘씩, 모두 여섯 개 둔다. 쓰기는 넷, 읽기는 셋이 답하면 된다. 가용 영역 하나를 잃어도 사본 넷이 남아 계속 쓰고, 영역 하나와 노드 하나를 더 잃어도 셋이 남아 읽어서 복구할 수 있다사본 6개 = AZ 3곳 × 2개 · 쓰기 4/6 · 읽기 3/6AZ 1사본사본AZ 2사본+1 장애AZ 3 — 장애사본사본장애남은 사본쓰기(≥4)읽기(≥3)정상6가능가능AZ 하나 장애4가능가능AZ 하나 + 노드 하나3불가가능4 + 3 > 6: 읽기는 마지막 쓰기를 꼭 만난다 · 4 + 4 > 6: 두 쓰기도 서로 만난다
AZ 하나가 통째로 멈춰도 사본 넷이 남아 계속 쓴다. AZ 하나와 다른 AZ의 노드 하나를 함께 잃어도(AZ+1) 셋이 남아 데이터를 잃지 않고, 읽기 쿼럼으로 상태를 확인해 빠진 사본을 다시 채운다.

왜 사본 셋이 아니라 여섯일까. 사본 셋에 쓰기 2/3 쿼럼이면 AZ 하나가 멈춘 상태에서 다른 노드 하나만 더 고장 나도 쿼럼이 깨진다. 큰 시스템에서는 디스크와 노드가 늘 어딘가에서 조용히 고장 나 있으므로, AZ 장애가 나는 순간 다른 곳의 고장 하나가 겹치는 일을 가정해야 한다. 그래서 논문은 "AZ 하나 + 노드 하나"를 잃어도 데이터를 잃지 않는 것을 목표로 삼았다. 다른 축은 고치는 속도다. 조각을 10GB로 작게 잘라 두면 하나를 잃었을 때 다시 채우는 데 10Gbps 링크로 10초 남짓이면 되어, 그 짧은 틈에 두 번째 고장이 겹칠 확률이 낮아진다. 같은 원리로 저장 노드를 패치하거나 뜨거운 디스크에서 조각을 옮길 때도 사본 하나를 잠깐 빼는 것으로 처리한다.

Aurora 쓰기 단계 그림: 쓰기 인스턴스가 redo 로그 레코드만 AZ 세 곳의 저장 노드 여섯에 보낸다. 넷이 디스크에 적고 답하면 커밋을 확정한다. 저장 노드는 뒤에서 가십으로 빈 레코드를 채우고 로그를 페이지로 합쳐 S3에 백업하며, 읽기 복제본은 같은 저장 볼륨을 읽는다쓰기 인스턴스redo 로그 레코드커밋 확정 → 앱에 답읽기 복제본 (최대 15)캐시한 페이지만 고침로그 스트림같은 볼륨에서 읽기AZ 1AZ 2AZ 3저장 노드받는 중ACK페이지로 합침페이지 제공저장 노드받는 중ACK페이지로 합침페이지 제공저장 노드받는 중ACK페이지로 합침페이지 제공저장 노드받는 중늦음가십으로 채움페이지 제공저장 노드받는 중ACK페이지로 합침페이지 제공저장 노드받는 중늦음가십으로 채움페이지 제공Amazon S3 백업 (뒤에서 주기적으로)보호 그룹 하나 = 10GB 조각 하나의 사본 여섯
1 / 6

트랜잭션이 커밋을 요청한다. 쓰기 인스턴스는 바뀐 데이터 페이지를 쓰지 않고, 무엇을 바꿨는지 적은 redo 로그 레코드만 만든다.

Aurora의 쓰기. 계산 쪽은 로그만 보내고 여섯 중 넷의 답을 기다린다. 페이지를 만드는 일은 저장 계층이 뒤에서 한다.

저장 노드는 로그 레코드를 받아 디스크에 적고 답하는 데까지만 앞에서 한다. 빠진 레코드를 이웃 노드와 가십으로 채우고, 로그를 페이지로 합치고, S3로 백업하고, 오래된 버전을 지우고, 페이지 체크섬을 검사하는 일은 모두 뒤에서 한다. 앞길이 짧으니 지연이 줄고, 저장 노드가 바쁠 때는 뒷일을 미루면 된다. 논문의 실험에서는 같은 쓰기 부하에서 미러링한 MySQL보다 트랜잭션을 35배 더 처리했고, 트랜잭션 하나에 쓰는 I/O는 7.7배 적었다.

이 구조는 다른 기능도 바꿨다. 복구: 전통적인 데이터베이스는 재시작할 때 마지막 체크포인트 뒤의 로그를 모두 다시 적용해야 해서 오래 걸린다. Aurora에서는 로그를 페이지로 바꾸는 일이 저장 계층에서 늘 진행 중이라, 재시작한 인스턴스는 저장 노드들에 물어 어디까지가 빠짐없이 내구화됐는지(볼륨의 일관된 지점)만 정하고 바로 문을 연다. 읽기: 평소에는 읽기에도 쿼럼을 쓰지 않는다. 쓰기 인스턴스는 어느 저장 노드가 어디까지 받았는지 알고 있으므로 최신인 노드 한 곳에서 읽는다. 읽기 쿼럼은 복구할 때만 쓴다. 읽기 복제본: 복제본은 같은 저장 볼륨을 읽으므로 데이터를 따로 복사하지 않는다. 쓰기 인스턴스가 보내는 로그 스트림으로 자기 캐시의 페이지만 고치면 된다. 그래서 복제본을 최대 15개까지 빠르게 붙일 수 있다.

한계도 같은 자리에서 나온다. 쓰기 인스턴스는 여전히 하나라서, 쓰기 처리량은 그 인스턴스 크기에서 멈춘다. 저장은 자동으로 커지며 최근 엔진 버전은 클러스터당 256TiB까지 늘었다. 쓰기까지 여러 대로 나누려면 아래의 Spanner 계열 설계가 필요하고, AWS도 그 방향으로 Aurora DSQL이라는 별도 분산 SQL 서비스를 냈다.

DynamoDB: 파티션마다 리더를 둔 키-값 저장소

이름이 비슷해서 자주 섞이지만 Dynamo와 DynamoDB는 다른 시스템이다. Dynamo는 Amazon이 쇼핑 카트 같은 내부 서비스를 위해 만든 키-값 저장소로, SOSP 2007 논문 Dynamo: Amazon's Highly Available Key-value Store로 알려졌다. 일관된 해싱 링에 키를 흩고, 리더 없이 아무 노드나 쓰기를 받으며, 충돌하는 버전은 벡터 시계로 남겨 두었다가 앱이 합치게 했다. 이 논문은 Cassandra와 Riak 같은 오픈 소스에 큰 영향을 줬다(문서와 와이드 컬럼 노트).

DynamoDB는 2012년에 나온 완전 관리형 서비스다. Dynamo의 교훈을 가져왔지만 내부는 상당히 다르다. 구조는 USENIX ATC 2022 논문 Amazon DynamoDB: A Scalable, Predictably Performant, and Fully Managed NoSQL Database Service에 처음 자세히 공개됐다. 테이블은 파티션 키를 해시한 값의 범위로 파티션에 나뉘고, 파티션 하나는 AZ 여러 곳에 흩어진 복제본들의 복제 그룹이다. 복제 그룹은 Multi-Paxos로 리더를 하나 뽑는다. 쓰기는 리더만 받는다. 리더가 WAL 레코드를 만들어 다른 복제본에 보내고, 과반이 자기 WAL에 적으면 앱에 답한다.

DynamoDB의 파티션: 요청 라우터가 파티션 키를 해시해 파티션을 고른다. 파티션마다 가용 영역 세 곳에 복제본이 있고 그중 하나가 리더다. 쓰기와 강한 일관성 읽기는 리더가 받고, 최종 일관성 읽기는 아무 복제본이나 받는다AZ aAZ bAZ cPutItem(user#42)요청 라우터hash(파티션 키)P1리더WAL + B-트리복제본WAL + B-트리복제본WAL + B-트리P2복제본WAL + B-트리리더WAL + B-트리복제본WAL + B-트리P3복제본WAL + B-트리복제본WAL + B-트리리더WAL + B-트리쓰기·강한 일관성 읽기 → 리더 (셋 중 둘이 로그를 적으면 응답)최종 일관성 읽기 → 아무 복제본 (방금 쓴 값이 늦게 보일 수 있음)
요청 라우터는 파티션 키를 해시해 파티션을 찾고, 그 파티션의 리더에게 쓰기를 보낸다. 파티션마다 리더가 다른 AZ에 있을 수 있어 부하가 고르게 퍼진다.
Dynamo (2007 논문)DynamoDB (2022 논문)
데이터 배치일관된 해싱 링과 가상 노드해시 키 범위로 나눈 파티션, 위치는 메타데이터 서비스가 관리
복제와 쓰기리더 없음. 느슨한 쿼럼(sloppy quorum)과 힌트 전달로 늘 받음파티션마다 Multi-Paxos 리더. 리더가 순서를 정하고 과반이 적으면 응답
충돌벡터 시계로 여러 버전을 남기고 앱이 합침리더가 순서를 정해 항목마다 한 버전
운영서비스 팀이 직접 설치·조정완전 관리형, 여러 고객이 함께 쓰는 서비스

리더가 있으니 읽기 일관성을 고를 수 있다. 공식 문서대로 기본은 최종 일관성 읽기로, 아무 복제본이나 답하므로 방금 쓴 값이 잠깐 안 보일 수 있고 비용은 절반이다. 요청에 ConsistentRead를 켜면 강한 일관성 읽기가 되어 리더가 답한다. 글로벌 보조 인덱스(GSI)는 따로 비동기로 채워지므로 최종 일관성 읽기만 된다. 리전 여러 곳에 복제하는 전역 테이블은 오래도록 리전 사이가 최종 일관성이었는데, 2025년에 쓰기를 다른 리전에 동기로 복제하는 다중 리전 강한 일관성(MRSC) 방식이 정식으로 나왔다. 일관성 모델의 이름들은 복제·샤딩·합의 노트에 있다.

DynamoDB가 가장 공들인 것은 이름 그대로 "예측 가능한 성능"이다. 파티션 하나가 낼 수 있는 처리량에는 상한이 있어, 공식 문서 기준 초당 읽기 3,000단위와 쓰기 1,000단위를 넘으면 그 파티션의 요청이 거절(throttling)된다. 초기에는 테이블에 준 처리량을 파티션 수로 똑같이 나눠서, 한 키에 몰리는 "뜨거운 파티션"이 생기면 테이블 전체 여유가 있어도 거절됐다. 그래서 쓰지 않은 용량을 최대 5분치 모아 두는 버스트, 바쁜 파티션에 몫을 더 주는 적응형 용량(adaptive capacity), 자주 쓰는 항목을 다른 파티션으로 떼어 내는 분할이 차례로 붙었다. 논문은 마지막 단계로, 처리량 몫을 파티션이 아니라 테이블 전체에서 토큰 버킷으로 관리하는 전역 입장 제어(global admission control)를 설명한다. 그래도 결국 파티션 키를 고르게 잘 고르는 것이 성능의 대부분을 정한다. 키 설계는 키-값 노트의 원리와 같다.

Google Spanner: 시계를 다시 만들어 전 세계 트랜잭션을 하다

Spanner는 Google이 광고 시스템 같은 곳에 쓰려고 만든, 여러 대륙에 걸친 관계형 데이터베이스다. OSDI 2012 논문 Spanner: Google's Globally-Distributed Database가 설계를 공개했고, 지금은 Google Cloud에서 서비스로 판다. 데이터는 키 범위로 잘린 분할(split)들로 나뉘고, 분할마다 여러 데이터센터에 복제본을 두어 Paxos 그룹 하나로 묶는다. 그룹의 리더가 쓰기와 락을 맡고, 과반이 받으면 커밋이 기록된다. 두 그룹에 걸친 트랜잭션은 리더들끼리 2단계 커밋을 한다. Paxos 그룹이 참가자라서, 참가자 하나가 멈춰도 2PC가 오래 막히지 않는다는 점이 보통의 2PC와 다르다.

Spanner의 데이터 배치: 키 범위로 나눈 분할마다 데이터센터 세 곳에 복제본을 두고 Paxos 그룹으로 묶는다. 그룹마다 리더가 하나 있고, 두 분할에 걸친 트랜잭션은 두 리더가 2단계 커밋으로 맞춘다데이터센터 A데이터센터 B데이터센터 C분할 1키 A–MPaxos 리더쓰기·락·타임스탬프복제본과반이 받으면 커밋복제본과반이 받으면 커밋분할 2키 N–Z복제본과반이 받으면 커밋Paxos 리더쓰기·락·타임스탬프복제본과반이 받으면 커밋리더끼리 2단계 커밋(2PC)점선 상자 하나 = Paxos 그룹 하나. 과반이 살아 있으면 데이터센터 하나를 잃어도 계속 쓴다.데이터센터마다 GPS 수신기와 원자시계를 단 타임 마스터가 있어 TrueTime을 제공한다.
분할마다 Paxos 그룹 하나가 되고, 그룹마다 리더가 있다. 리더가 서로 다른 데이터센터에 있어도 두 분할에 걸친 트랜잭션은 2단계 커밋으로 원자적으로 끝난다.

어려운 부분은 순서다. 서울에서 커밋한 트랜잭션 T1이 끝난 뒤 런던에서 T2가 시작했다면, 데이터베이스의 타임스탬프로도 T1이 T2보다 앞서야 한다. 이를 외부 일관성(external consistency)이라 하며, 트랜잭션 단위의 선형성(linearizability)과 같은 말이다. 서버마다 시계가 조금씩 어긋나 있으니 그냥 각자 시계로 타임스탬프를 찍으면 이 순서가 뒤집힐 수 있다.

Spanner의 답은 시계가 틀릴 수 있다는 사실을 API에 드러내는 것이다. TrueTime의 TT.now()는 시각 하나가 아니라 구간 [earliest, latest]를 돌려주고, 참 시각이 그 안에 있음을 보장한다. 데이터센터마다 GPS 수신기와 원자시계를 단 타임 마스터를 두고 각 서버가 이들에 주기적으로 시각을 맞추므로 구간이 좁게 유지된다. 논문이 보고한 오차 ε는 1~7ms 사이를 오가는 톱니 모양이었다. 이 구간을 받아 리더는 커밋 타임스탬프를 latest로 고르고, 그 시각이 확실히 지나갈 때까지 기다린 뒤에 응답한다. 이것이 커밋 대기(commit wait)다.

Spanner 커밋 대기 단계 그림: 커밋을 시작할 때 TrueTime 구간의 latest를 커밋 타임스탬프 s로 고르고, Paxos 복제와 함께 TrueTime 구간의 earliest가 s에 닿을 때까지 약 두 배 오차만큼 기다린 뒤 응답한다. 그 뒤 시작한 트랜잭션은 s보다 큰 타임스탬프를 받는다TT.now() = [earliest, latest]s = latestPaxos 복제 (함께 진행)커밋 대기 ≈ 2ε응답 순간 TT.now()다음 T2참 시각 →시작s응답
1 / 5

커밋하려는 순간 TT.now()는 시각 하나가 아니라 구간 [earliest, latest]를 돌려준다. 참 시각은 그 안 어딘가에 있고, 구간의 반폭이 오차 ε다.

Spanner의 커밋 대기. 시계 오차를 모르는 척하지 않고 구간으로 받아, 그 구간이 지나갈 때까지 기다린다. 오차가 작을수록 기다림도 짧다.

커밋 대기는 오차 ε의 약 두 배 시간을 쓴다. 다만 Paxos 복제와 함께 기다리므로, 복제에 그만큼 걸린다면 추가로 잃는 시간은 거의 없다. 시계를 좋게 만드는 투자가 곧 지연을 줄이는 셈이다. 이렇게 타임스탬프가 실제 시간 순서를 따르면 읽기 전용 트랜잭션이 아주 싸진다. 읽기 시작할 때 타임스탬프 하나를 고르고, 그 시점의 스냅숏을 락 없이 읽는다. 그 타임스탬프까지 기록을 받은 복제본이면 어디서나 답할 수 있어 가까운 데이터센터에서 읽고, 과거 타임스탬프를 지정한 오래된 읽기(stale read)는 리더와 연락하지 않아도 된다. 스냅숏과 MVCC는 동시성과 격리 수준 노트에 있다. Google Cloud의 TrueTime과 외부 일관성 문서도 같은 보장을 설명한다.

BigQuery와 Snowflake: 오브젝트 스토리지 위의 분석

분석용 데이터베이스는 큰 표의 몇 열을 훑어 집계한다. 그래서 행 단위가 아니라 열 단위로 저장하면 필요한 열만 읽고 같은 종류 값끼리 잘 압축된다. 열 저장의 원리는 분석과 검색 노트에 있다. 클라우드의 분석 서비스는 이 열 파일을 싸고 무한히 커지는 분산 저장소에 두고, 계산은 쿼리가 올 때만 빌려 쓰는 쪽으로 갔다.

BigQuery의 엔진은 Dremel이다. VLDB 2010 논문 Dremel: Interactive Analysis of Web-Scale Datasets은 중첩된 레코드를 열로 쪼개 저장하는 방법(반복 수준과 정의 수준으로 구조를 복원)과, 쿼리를 나무 모양 서버들로 퍼뜨려 수조 행을 몇 초에 훑는 방법을 보였다. 10년 뒤의 회고 논문 Dremel: A Decade of Interactive SQL Analysis at Web Scale(VLDB 2020)은 그 사이 바뀐 것을 정리한다. 저장을 로컬 디스크에서 분산 파일 시스템(Colossus)으로 옮겨 계산과 나눴고, 단계 사이의 중간 결과를 담는 셔플을 별도의 분산 메모리 계층으로 떼어 냈으며, 고정된 서버 나무 대신 쿼리마다 단계를 짜서 작업자(슬롯)를 그때그때 배정한다. 사용자가 서버를 하나도 고르지 않는 서버리스 방식이 여기서 나왔다.

BigQuery의 쿼리 실행: 분산 파일 시스템에 열 단위로 저장된 테이블에서 아래 단계 작업자들이 필요한 열만 읽어 거르고, 중간 결과를 분산 메모리 셔플 계층에 쓴다. 위 단계 작업자들이 셔플에서 읽어 모으고 조정자가 결과를 돌려준다조정자: 계획을 세우고 결과를 모음2단계: 모아 집계1단계: 읽고 거름2단계: 모아 집계1단계: 읽고 거름2단계: 모아 집계1단계: 읽고 거름2단계: 모아 집계1단계: 읽고 거름셔플 계층: 단계 사이 중간 결과를 담는 분산 메모리날짜 열국가 열상품 열금액 열메모 열주소 열Colossus의 열 단위 파일(Capacitor): 쿼리가 쓰는 열만 읽는다
BigQuery의 쿼리 실행. 1단계 작업자가 필요한 열만 읽어 거른 뒤 셔플 계층에 쓰고, 2단계 작업자가 거기서 읽어 모은다. 셔플을 따로 두니 작업자가 죽어도 그 몫만 다시 하고, 단계마다 작업자 수를 바꿀 수 있다.

서버리스라서 요금도 다르게 매긴다. BigQuery 요금은 쿼리가 읽은 바이트로 내는 온디맨드 방식과, 미리 확보한 슬롯(계산 용량)으로 내는 방식으로 나뉜다. 온디맨드에서는 SELECT *로 모든 열을 읽으면 그만큼 돈이 나간다. 그래서 날짜로 테이블을 나누고(partitioning) 자주 거르는 열로 묶어(clustering) 읽을 블록을 줄이는 것이 성능이자 비용 관리다.

Snowflake는 처음부터 클라우드를 전제로 만든 데이터 웨어하우스다. SIGMOD 2016 논문 The Snowflake Elastic Data Warehouse가 세 층 구조를 설명한다. 맨 아래 오브젝트 스토리지(S3 등)에 테이블 파일이 있고, 가운데에는 가상 웨어하우스라는 계산 묶음이 있으며, 맨 위 클라우드 서비스가 메타데이터, 쿼리 최적화, 트랜잭션, 접근 제어를 맡는다. 가상 웨어하우스는 서로 자원을 나누지 않으므로, 밤새 도는 적재 작업과 낮의 대시보드를 다른 웨어하우스에 두면 서로 느려지지 않으면서 같은 데이터를 읽는다.

Snowflake의 세 층: 맨 위 클라우드 서비스가 메타데이터, 옵티마이저, 트랜잭션, 보안을 맡는다. 가운데에는 크기가 다른 가상 웨어하우스 세 개가 서로 독립적으로 돌며 각자 로컬 SSD 캐시를 둔다. 맨 아래 오브젝트 스토리지에 모든 웨어하우스가 함께 읽는 마이크로 파티션 파일이 있다클라우드 서비스메타데이터 · 옵티마이저 · 트랜잭션 · 접근 제어 (늘 켜져 있음)ETL용 (L)노드 8대 · 로컬 SSD 캐시BI 대시보드용 (XS)노드 1대 · 로컬 SSD 캐시분석가용 (M)노드 4대 · 로컬 SSD 캐시오브젝트 스토리지: 변하지 않는 마이크로 파티션 파일
Snowflake의 세 층. 웨어하우스는 크기가 서로 다르고 서로 자원을 나누지 않는다. 쓰지 않으면 멈추고, 각자 로컬 SSD에 자주 읽는 파일을 캐시한다.

웨어하우스 노드는 자주 읽는 테이블 파일을 로컬 SSD에 캐시한다. 논문은 같은 파일이 늘 같은 노드에 가도록 일관된 해싱으로 파일을 노드에 나누고, 일이 먼저 끝난 노드가 느린 노드의 파일을 가져가 처리(file stealing)한다고 설명한다. 요금도 이 구조를 따른다. 웨어하우스 문서대로 웨어하우스가 켜져 있는 동안 초 단위로 크레딧을 내고(켤 때마다 최소 60초), 쉬면 자동으로 멈춘다. 크기를 한 단계 올리면 시간당 크레딧이 두 배가 된다.

테이블은 마이크로 파티션이라는 변하지 않는 파일들로 나뉜다. 공식 문서 기준 하나에 압축 전 50~500MB가 들고, 안에서는 열마다 따로 압축해 저장한다. 고칠 때는 파일을 바꾸지 않고 새 파일을 쓴 뒤 메타데이터가 가리키는 파일 목록을 바꾸므로, 옛 파일 목록을 남겨 두기만 하면 스냅숏 격리와 과거 시점 조회(Time Travel)가 저절로 된다. 그리고 파티션마다 열의 최솟값·최댓값 같은 통계를 메타데이터에 들고 있어, 조건과 범위가 겹치지 않는 파티션은 열지 않고 건너뛴다. 이것이 가지치기(pruning)다. 인덱스를 따로 만들지 않는 대신, 데이터가 들어온 순서가 곧 성능이 된다.

마이크로 파티션 가지치기: 날짜가 12일부터 16일 사이인 행을 찾을 때, 파티션 여섯 개의 최솟값과 최댓값을 보고 범위가 겹치는 MP3, MP4만 읽고 나머지는 열지 않는다WHERE 날짜 BETWEEN 12 AND 16파티션마다 '날짜' 열의 최솟값·최댓값을 메타데이터로 들고 있다MP1최소 1최대 6건너뜀MP2최소 5최대 11건너뜀MP3최소 10최대 14읽음MP4최소 15최대 20읽음MP5최소 21최대 27건너뜀MP6최소 26최대 31건너뜀여섯 중 2개만 연다. 데이터를 날짜 순서로 넣을수록 범위가 덜 겹쳐 더 많이 건너뛴다
날짜 조건과 최솟값·최댓값 범위가 겹치는 두 파티션만 읽는다. 날짜가 뒤섞여 들어가 모든 파티션의 범위가 넓어지면 아무것도 건너뛰지 못한다. 그래서 자주 거르는 열로 다시 정렬해 묶는 클러스터링 키를 둔다.

다른 갈래: 분산 SQL 오픈 소스, Neon, 관리형 오픈 소스

Spanner를 닮은 분산 SQL: Spanner 논문 뒤로 비슷한 설계의 데이터베이스가 여럿 나왔다. 셋 다 키 범위를 작은 조각으로 나누고, 조각마다 Raft 그룹으로 복제하며, 그 위에 분산 트랜잭션을 올린다. 차이는 주로 시계에 있다. TrueTime 같은 특수 하드웨어가 없으니 다른 방법으로 타임스탬프를 정한다.

  • CockroachDB는 물리 시계와 논리 카운터를 섞은 하이브리드 논리 시계(HLC)를 쓰고, 시계 어긋남의 최댓값을 설정해 두고 그 불확실 구간에 걸린 읽기를 다시 시도하는 방식으로 커밋 대기를 피한다. PostgreSQL 통신 규약과 호환된다. 오픈 소스로 시작했지만 2024년 11월부터 무료 Core 판을 없애고 단일 Enterprise 라이선스로 바꿔, 지금은 소스를 볼 수 있는 상용 라이선스다.
  • TiDB는 MySQL과 호환되는 SQL 계층, Raft로 복제하는 키-값 저장소 TiKV, 그리고 전역 타임스탬프를 하나씩 나눠 주는 배치 관리자(PD)로 나뉜다. 시계 대신 중앙의 타임스탬프 발급기를 두는 쪽을 골랐다. 분석용 열 저장 복제본(TiFlash)도 붙일 수 있다.
  • YugabyteDB는 PostgreSQL의 쿼리 계층을 가져와 Raft 기반 분산 저장소(DocDB) 위에 올렸고, 하이브리드 시계를 쓴다. 아파치 2.0 라이선스다.

Neon: PostgreSQL을 Aurora처럼 나누기: Neon은 오픈 소스 PostgreSQL의 저장 계층을 갈아 끼워 저장과 계산을 나눴다. 구조 문서대로 계산 노드는 커밋할 때 WAL을 로컬 디스크 대신 세이프키퍼들에 보내고, 세이프키퍼는 Paxos로 과반이 받으면 커밋으로 친다. 페이지서버가 WAL을 페이지로 바꿔 오브젝트 저장소에 두고, 계산 노드는 캐시에 없는 페이지를 "이 LSN 시점의 이 페이지"로 페이지서버에 묻는다. Aurora와 같은 "로그가 곧 데이터베이스" 생각이지만, 기록을 LSN으로 다 남기므로 데이터를 복사하지 않고 과거 시점에서 갈라지는 브랜치를 만들 수 있고, 쓰지 않는 계산 노드는 0까지 줄인다. Neon은 2025년에 Databricks에 인수되었다. Google Cloud의 AlloyDB도 PostgreSQL의 저장 계층을 분산 저장소로 바꾼 같은 계열이다.

Neon의 구조: PostgreSQL 계산 노드가 WAL을 세이프키퍼 세 대에 보내고, 세이프키퍼는 Paxos로 WAL을 지킨다. 페이지서버가 WAL을 받아 페이지로 바꿔 오브젝트 저장소에 두고, 계산 노드는 필요한 페이지를 LSN을 붙여 페이지서버에 묻는다PostgreSQL 계산로컬 상태 없음세이프키퍼 (Paxos)WALWAL 보관WAL 보관WAL 보관페이지서버WAL → 페이지오브젝트저장소페이지 읽기: "LSN 시점의 이 페이지를 달라"기록이 LSN으로 남아 있으니, 브랜치·시점 복구는 과거 LSN에서 갈라 읽으면 된다
Neon의 구조. 커밋은 세이프키퍼 과반이 WAL을 받는 순간 끝나고, 페이지를 만드는 일은 페이지서버가 뒤에서 한다. 계산 노드는 상태가 없어 멈췄다 켜도 데이터가 그대로다.

관리형 오픈 소스: Amazon RDS나 Google Cloud SQL은 PostgreSQL·MySQL 같은 엔진을 거의 그대로 가상 머신과 블록 스토리지 위에서 대신 운영해 준다. 백업, 패치, 장애 조치를 맡아 주지만 내부 구조는 처음 그림의 왼쪽, 서버마다 자기 디스크를 둔 모양이다. 엔진이 같아 옮기기 쉽고 동작을 예측하기 쉬운 대신, 저장과 계산을 따로 늘리는 이점은 없다. 클라우드 네이티브 설계는 그 반대로, 이점이 큰 만큼 그 서비스에만 있는 동작과 요금 구조에 묶인다.

고르는 법

먼저 무엇을 묻는 데이터베이스인지가 갈래를 정한다. 키로 한 건씩 읽고 쓰는지, SQL과 트랜잭션이 필요한지, 큰 표를 훑어 집계하는지에 따라 아예 다른 서비스가 된다. 그다음 같은 갈래 안에서 일관성, 늘리는 단위, 요금 방식, 그리고 나중에 옮기기 어려운 정도를 비교한다.

클라우드 데이터베이스 고르는 길: 키로 한 건씩 아주 큰 규모로 다루면 DynamoDB나 Bigtable, 한 리전에서 SQL과 트랜잭션이면 관리형 PostgreSQL·MySQL이나 Aurora·AlloyDB·Neon, 여러 리전에서 쓰면 Spanner·Aurora DSQL·CockroachDB·YugabyteDB, 큰 표를 훑는 분석이면 BigQuery·Snowflake·Redshift다무엇을 묻나?키로 한 건씩 읽고 씀규모가 아주 큼DynamoDB · Bigtable키 설계가 곧 성능SQL·트랜잭션한 리전에서 씀RDS·Cloud SQLAurora·AlloyDB·NeonSQL·트랜잭션여러 리전에서 씀Spanner · Aurora DSQLCockroachDB · YugabyteDB큰 표를 훑어 집계분석·보고서BigQuery · SnowflakeRedshift갈래 안에서는 아래 표의 일관성·늘리는 단위·요금 방식·묶임 정도를 비교한다
무엇을 묻는지가 먼저다. SQL이 필요하면 쓰기가 한 리전에서 끝나는지, 여러 리전에서 동시에 일어나는지로 다시 갈린다.
서비스모델일관성늘리는 단위요금 방식묶임
RDS·Cloud SQL관계형(오픈 소스 엔진 그대로)주 서버는 강함, 읽기 복제본은 비동기인스턴스 크기, 읽기 복제본인스턴스 시간 + 저장 용량낮음: 같은 엔진이라 옮기기 쉽다
Aurora관계형(MySQL·PostgreSQL 호환)쓰기 인스턴스는 강함, 복제본은 조금 늦음쓰기 인스턴스 하나 + 복제본, 저장은 자동인스턴스(또는 서버리스 용량) + 저장 + I/O낮음~중간: SQL은 호환, 빠른 복제본·복구는 Aurora에만
DynamoDB키-값·문서기본 최종 일관성, 요청마다 강한 일관성 선택파티션(자동 분할)요청 수(온디맨드) 또는 미리 정한 처리량 + 저장높음: 고유 API와 키 설계
Spanner관계형(GoogleSQL·PostgreSQL 방언)전역 외부 일관성노드·처리 단위, 분할은 자동계산 용량 + 저장중간~높음: TrueTime 기반 보장은 대체가 어렵다
BigQuery분석(열 저장)쿼리마다 한 시점의 스냅숏슬롯, 서버리스로 자동 배정읽은 바이트 또는 슬롯 용량 + 저장중간: SQL은 표준에 가깝지만 운영 방식이 고유
Snowflake분석(열 저장)스냅숏 격리가상 웨어하우스의 크기와 개수웨어하우스가 켜진 시간(크레딧) + 저장중간: 여러 클라우드에서 돌지만 Snowflake 전용 서비스

몇 가지 경험칙이 있다. 쓰기가 한 리전에서 끝나는 서비스라면 대부분 관리형 PostgreSQL이나 MySQL로 시작해, 읽기 복제본이나 저장 용량이 부족해질 때 Aurora 같은 분리형으로 옮기면 된다. 엔진이 같아 옮기기 쉽다. 쓰기 자체가 한 대를 넘거나 여러 리전에서 동시에 강한 일관성으로 써야 할 때 분산 SQL을 검토한다. 커밋마다 합의와 리전 사이 왕복이 들어가므로 지연이 늘어나는 것을 감수해야 한다. DynamoDB는 접근 패턴을 미리 알고 키를 그에 맞춰 설계할 수 있을 때 가장 잘 맞고, 나중에 새로운 질문이 생기면 테이블을 다시 짜야 할 수 있다. 분석은 운영 데이터베이스에서 떼어 BigQuery나 Snowflake로 옮기는 편이, 운영 쪽 성능과 분석 쪽 비용을 모두 지키는 길이다. 쿼리 계획과 인덱스의 기본은 인덱스와 쿼리 실행 노트에 있다.