PostgreSQL vs Aurora: 클라우드가 뒤집은 DB 내부
WAL·복제·체크포인트가 Aurora에서 어떻게 재설계됐나
이 시리즈는 일곱 편에 걸쳐 PostgreSQL의 내부 동작을 파헤쳤다 — WAL, 복제, MVCC/VACUUM, 메모리 구조까지. 이 메커니즘들은 수십 년간 검증된 설계지만, 모두 하나의 전제를 공유한다 — 데이터베이스는 디스크가 붙은 한 대의 서버다.
이 편에서는 클라우드가 그 전제를 깨뜨린 첫 사례, Amazon Aurora를 본다. Aurora는 PostgreSQL/MySQL과 호환되지만, 그 아래의 스토리지·복제·복구 계층은 완전히 다시 설계되었다. 마케팅이 아니라, 앞에서 본 내부 동작이 클라우드에서 왜·어떻게 뒤집혔는지를 비교한다.
전통 PostgreSQL의 클라우드 스케일 한계
먼저 단일 노드 PostgreSQL이 클라우드에서 부딪히는 벽을 정리하자. 모두 이 시리즈에서 다룬 내용이다.
- 한 노드가 모든 쓰기를 짊어진다. Part 2에서 봤듯, 커밋 경로에서 WAL을 flush하고, 백그라운드로 더티 데이터 페이지를 쓰고, 체크포인트마다 더티 버퍼를 몰아 내리고, 체크포인트 직후엔
full_page_writes로 8KB 페이지 전체를 WAL에 또 쓴다. 같은 데이터가 여러 번 디스크로 향한다(쓰기 증폭, write amplification). - 복제는 standby가 WAL을 받아 재생한다. Part 5에서 봤듯, 프라이머리가 만든 WAL을 standby가 그대로 REDO 재생한다. standby는 프라이머리의 작업을 고스란히 한 번 더 수행하며, 그만큼 lag과 리소스가 든다.
- 확장은 노드를 키우는 것(scale-up)뿐. Part 6의 프로세스·메모리 구조는 한 서버 안에서 동작한다. 스토리지를 컴퓨트와 따로 늘릴 수 없다.
전통 PostgreSQL/MySQL에서 한 번의 변경이 디스크와 네트워크에 만들어내는 쓰기를 그려보면 이렇다.
[ 전통: HA를 위한 동기 미러링 구성 ]
변경 1건
├─ WAL(redo) 기록
├─ 체크포인트 시 더티 데이터 페이지 flush
├─ full_page_writes / 더블라이트 버퍼
└─ 위 모두를 동기 복제로 다른 AZ에 또 전송
→ 같은 변경이 디스크·네트워크에 여러 벌로 증폭
클라우드에서 진짜 비싸지고 느려지는 지점은 네트워크다. HA를 위해 여러 AZ에 동기 복제하면, 커밋 한 번이 무거운 데이터 페이지들을 네트워크 너머로 여러 번 실어 나른다. Aurora의 출발점이 바로 여기다.
[!NOTE] Aurora SIGMOD 2017 논문(Verbitski et al.)의 핵심 주장: 고처리량 데이터베이스의 병목은 더 이상 CPU나 디스크가 아니라 네트워크다. 그러니 네트워크로 무엇을 보내느냐를 다시 설계해야 한다.
Aurora의 핵심 발상: “The log is the database”
Aurora의 재설계는 두 문장으로 압축된다.
[!IMPORTANT] ① 스토리지와 컴퓨트를 분리한다. 데이터베이스 엔진(컴퓨트)은 디스크를 직접 갖지 않고, 전용 분산 스토리지 서비스 위에 올라탄다. ② 로그가 곧 데이터베이스다(The log is the database). 컴퓨트는 데이터 페이지를 디스크에 쓰지 않는다. 오직 redo 로그 레코드만 스토리지로 보내고, 데이터 페이지는 스토리지 계층이 그 로그로부터 구체화(materialize)한다.
전통 PostgreSQL에서 WAL은 “데이터 페이지를 복구하기 위한 보조 기록”이었다. Aurora에서는 그 관계가 역전된다 — WAL(redo 로그)이 1차 진실이고, 데이터 페이지는 로그에서 파생된 캐시일 뿐이다. Part 2에서 “WAL이 진실의 원본”이라 했던 명제를 Aurora는 아키텍처 전체로 밀어붙인 셈이다.
[ 전통 PostgreSQL ]
컴퓨트 = 스토리지 (한 노드)
커밋 → WAL flush + (나중에) 데이터 페이지 쓰기 + 체크포인트 + FPI
[ Amazon Aurora ]
컴퓨트(DB 엔진) ──redo 로그 레코드만──> 분산 스토리지(6중 복제)
│
▼ 스토리지 노드가
redo를 적용해 페이지를
비동기로 구체화
redo 로그만 전송한다 — 쓰기 증폭의 제거
이 한 가지 결정이 쓰기 I/O 지형을 통째로 바꾼다. Aurora 컴퓨트 노드는 데이터 페이지를 절대 쓰지 않는다. 따라서 다음이 전부 사라진다.
| 전통 PostgreSQL/MySQL이 쓰는 것 | Aurora 컴퓨트가 쓰는 것 |
|---|---|
| WAL(redo) 로그 | redo 로그 레코드 (이것만) |
| 더티 데이터 페이지 (background writer) | ❌ 없음 |
| 체크포인트 시 더티 버퍼 일괄 flush | ❌ 없음 (체크포인트 자체가 없음) |
full_page_writes/더블라이트 버퍼 |
❌ 없음 (페이지를 안 쓰니 torn page 무의미) |
데이터 페이지를 만드는 일은 스토리지 노드가 맡는다. 스토리지는 받은 redo 레코드를 연속적·비동기적으로, 그리고 분산되어 적용한다. 어떤 페이지를 읽어야 하는데 아직 최신이 아니면, 그때 필요한 redo 레코드를 적용해 페이지를 구체화한다(materialize on demand).
논문의 벤치마크가 이 효과를 잘 보여준다. 여러 AZ에 걸쳐 EBS로 동기 미러링한 MySQL과 Aurora를 sysbench로 30분간 비교했을 때:
[!NOTE] Aurora는 같은 시간에 미러링 MySQL보다 약 35배 많은 트랜잭션을 처리했고, 데이터베이스 노드의 트랜잭션당 I/O는 약 7.7배 적었다 — 스토리지에서 데이터를 6중으로 복제해 쓰는데도 그렇다. 무거운 페이지 쓰기를 네트워크에서 걷어내고 가벼운 로그만 보냈기 때문이다.
분산 스토리지: 6중 복제와 쿼럼
Aurora 스토리지는 단순한 디스크가 아니라 다중 AZ에 걸친 분산·자가치유 서비스다. 구조는 다음과 같다.
- 데이터베이스 볼륨을 10GB 세그먼트(protection group)로 잘게 쪼갠다.
- 각 10GB 세그먼트를 3개 AZ에 걸쳐 6중으로 복제한다(AZ당 2개 복사본).
- 쓰기와 읽기는 쿼럼(quorum)으로 처리한다.
AZ-1 AZ-2 AZ-3
[copy1] [copy3] [copy5]
[copy2] [copy4] [copy6]
└──────────── 10GB 세그먼트의 6중 복제 ────────────┘
쓰기 쿼럼: 6개 중 4개 ACK (Vw = 4)
읽기 쿼럼: 6개 중 3개 (Vr = 3)
→ Vw + Vr = 7 > 6, Vw = 4 > 6/2 (쿼럼 교집합·과반 보장)
| 항목 | 값 |
|---|---|
| 복사본 수 | 6 (3 AZ × 2) |
| 쓰기 쿼럼 | 4 / 6 |
| 읽기 쿼럼 | 3 / 6 |
| 세그먼트 크기 | 10 GB |
| 쓰기 가용성 | 복사본 2개 손실까지 유지 (AZ 하나 통째 = 2개) |
| 읽기 가용성 | 복사본 3개 손실까지 유지 |
이 설계의 묘미는 AZ+1 내결함성이다. 쓰기 쿼럼이 4/6이므로, AZ 하나가 통째로(2개) 날아가도 남은 4개로 쓰기를 계속할 수 있다. 읽기 쿼럼은 3/6이라, AZ 하나(2개) + 추가 1개를 더 잃어도 읽기가 살아있다. 손상된 세그먼트는 다른 복사본에서 자동으로 재생성(self-healing)된다. 10GB라는 작은 단위로 쪼갰기에 한 세그먼트 복구가 빠르고, 복구 중에도 쿼럼이 유지된다.
[!NOTE] 전통 PostgreSQL은 Part 2의
full_page_writes로 torn page를 막았다. Aurora는 애초에 컴퓨트가 페이지를 쓰지 않으므로 torn page라는 문제 자체가 컴퓨트 계층에서 사라진다. 내구성은 쿼럼과 세그먼트 복제가 책임진다.
스토리지 노드는 무엇을 하는가
쓰기 지연을 결정하는 것은 “스토리지가 redo를 받아 영속화하고 ACK하기까지”다. 페이지를 만드는 무거운 작업은 그 경로 밖에서 비동기로 일어난다. 논문은 스토리지 노드의 작업을 여덟 단계로 정리하는데, 핵심은 앞의 두 단계만 포그라운드(지연에 영향)라는 점이다.
[ 스토리지 노드의 redo 처리 — 포그라운드는 ①②뿐 ]
① redo 레코드 수신 → 인메모리 큐에 적재 ┐ 포그라운드
② 디스크에 영속화하고 ACK 반환 ┘ (커밋 지연에 영향)
──────────────────────────────────────────────
③ 레코드 정리, 로그의 빈틈(gap) 식별 ┐
④ peer 노드와 가십(gossip)해 빈틈 채움 │
⑤ redo를 합쳐(coalesce) 데이터 페이지로 구체화 │ 백그라운드
⑥ 로그·페이지를 주기적으로 S3에 백업 │ (커밋 지연과 무관)
⑦ 오래된 페이지 버전 가비지 컬렉션 │
⑧ 페이지 CRC 검증 ┘
커밋이 빠른 이유가 여기서 분명해진다. 클라이언트가 기다리는 것은 ①②(작은 redo를 받아 디스크에 쓰고 ACK)뿐이고, Part 2에서 체크포인트가 하던 무거운 페이지 구체화는 ⑤로 밀려나 상시 백그라운드로 처리된다. 가십(④)으로 빈틈을 메우는 덕에, 어떤 스토리지 노드가 일부 로그 배치를 놓쳐도 동료에게서 받아 채운다.
커밋은 어떻게 확정되나 — 합의 없는 쿼럼
6중 복제라고 하면 보통 Paxos 같은 분산 합의를 떠올린다. Aurora는 이를 영리하게 피한다.
[!IMPORTANT] SQL 엔진이 단일 인스턴스라서, 로그 레코드의 순서(LSN)가 한 곳에서 미리 정해진다. 따라서 복제에 합의나 2단계 커밋(2PC)이 필요 없다 — 정해진 순서의 로그를 쿼럼으로 복제하기만 하면 된다.
컴퓨트는 몇 개의 LSN 경계로 일관성을 추적한다.
| 약어 | 이름 | 의미 |
|---|---|---|
| VCL | Volume Complete LSN | 그 이전 모든 로그 레코드의 가용성이 보장되는 최고 LSN |
| CPL | Consistency Point LSN | 미니 트랜잭션의 마지막 레코드(원자적 경계) |
| VDL | Volume Durable LSN | VCL 이하인 가장 높은 CPL — 컴퓨트의 일관성 보장점 |
커밋은 단순하다 — 4/6 쓰기 쿼럼이 충족되면 VCL이 오르고 그에 맞춰 VDL이 전진한다. VDL이 그 트랜잭션의 커밋 LSN 이상으로 올라가면 커밋이 확정된다. Part 2에서 커밋이 “WAL 레코드의 fsync”였던 것이, 여기서는 “redo 레코드의 쿼럼 영속화”로 확장됐다.
읽기는 한 걸음 더 영리하다. Aurora는 읽기에 쿼럼이 필요 없다. 각 세그먼트가 어디까지 받았는지를 컴퓨트가 추적하므로, 읽으려는 페이지의 VDL 이상을 가진 세그먼트 아무 하나에서 바로 읽는다. 쿼럼 읽기의 네트워크 비용마저 없앤 셈이다.
복제: 공유 스토리지 위의 read replica
Part 5에서 본 전통 스트리밍 복제와 Aurora 복제의 차이는 본질적이다.
- 전통 PostgreSQL — 프라이머리와 standby는 각자 자기 디스크를 가진다. 프라이머리의 WAL을 standby로 보내 standby가 REDO 재생해 자기 디스크에 데이터를 만든다. standby는 프라이머리의 쓰기를 사실상 반복한다.
- Aurora — 라이터와 리더(read replica)가 같은 분산 스토리지 볼륨을 공유한다. 리더는 데이터를 따로 재생해 만들 필요가 없다. 라이터가 보내주는 redo 로그 스트림은 리더의 버퍼 캐시를 갱신하는 용도일 뿐, 페이지는 공유 스토리지에서 읽는다.
| 항목 | 전통 PostgreSQL 복제 | Aurora 복제 |
|---|---|---|
| 스토리지 | 노드마다 별도 | 전 노드 공유 |
| 복제본이 하는 일 | WAL을 받아 REDO 재생 | 버퍼 캐시만 갱신(재생 불필요) |
| 데이터 복사 | 복제본 수만큼 늘어남 | 스토리지는 1벌(6중 복제) 공유 |
| 복제본 수 | 운영 부담에 비례해 제한 | 최대 15개 |
| 복제 지연 | 워크로드에 따라 수백 ms~초 | 보통 100ms 미만(수십 ms) |
스토리지를 공유하기 때문에 복제본을 추가해도 데이터를 새로 복사하지 않는다. 그래서 리더를 최대 15개까지 거의 공짜에 가깝게 붙일 수 있고, lag도 “내 디스크에 재생을 끝내야 하는” 전통 방식보다 훨씬 작다.
체크포인트가 없다 — 그리고 빠른 크래시 복구
Part 2에서 체크포인트는 “더티 페이지를 모두 내리고 redo point를 앞당겨 복구 시간을 줄이는 장치”였고, 크래시 복구는 “마지막 체크포인트부터 WAL을 재생(REDO)하는 과정”이었다. Aurora에서는 이 둘 다 모습이 다르다.
체크포인트가 없다. 컴퓨트가 데이터 페이지를 쓰지 않으니, 더티 페이지를 몰아 내릴 일도 없다. 스토리지로 가는 것은 작은 redo 레코드뿐이다. Part 1의 VACUUM이나 체크포인트가 만들던 주기적 I/O 폭증이 컴퓨트에서 사라진다.
크래시 복구가 빠르다. 전통 PostgreSQL은 재시작 시 마지막 체크포인트부터 WAL 끝까지를 직렬로 재생해야 데이터베이스가 열린다. WAL이 길면 복구가 길어진다. Aurora는 redo 적용이 이미 스토리지 계층에서 연속·병렬·분산으로 일어나고 있으므로, 컴퓨트가 죽었다 살아나도 “긴 재생 구간”을 기다릴 필요가 없다. 복구 작업이 평상시 전경 처리에 녹아 있는 셈이다.
[!NOTE] AWS는 이 구조 덕에 데이터베이스 재시작이 대부분 60초 이내라고 설명한다. 복구가 “재시작 시점의 한 번에 몰린 작업”이 아니라 “스토리지가 늘 하던 일”이기 때문이다. Part 2에서 본 REDO 재생이 한 노드의 startup 과정에서 → 스토리지 플릿 전체의 상시 작업으로 옮겨갔다고 보면 정확하다.
트레이드오프와 한계
Aurora가 모든 면에서 우월한 것은 아니다. 재설계에는 대가와 제약이 따른다.
- 완전한 PostgreSQL은 아니다. 호환은 높지만, 지원하는 메이저 버전이 커뮤니티보다 늦을 수 있고, 스토리지 계층을 건드리는 일부 확장이나 기능은 제약이 있다. “PostgreSQL을 쓴다”기보다 “PostgreSQL 호환 엔진을 쓴다”에 가깝다.
- 쓰기 지연(write latency)의 성격이 다르다. 모든 커밋은 redo를 여러 AZ에 걸쳐 4/6 쿼럼으로 영속화해야 한다. 단일 로컬 디스크 fsync보다 네트워크 왕복이 끼므로, 처리량은 크게 늘어도 개별 커밋의 지연 특성은 워크로드에 따라 달라진다.
- 비용. 스토리지는 쓴 만큼 과금되고 I/O와 컴퓨트 비용 모델이 일반 RDS와 다르다. 작고 단순한 워크로드에는 과할 수 있다.
- 워크로드 적합성. 읽기 확장과 HA가 중요한 대규모 OLTP에 빛나지만, 모든 경우의 정답은 아니다.
확장 기능도 짚어두자.
| 기능 | 요지 |
|---|---|
| Aurora Serverless v2 | 부하에 따라 용량(ACU)을 잘게 자동 스케일. 스토리지-컴퓨트 분리라서 가능한 모델 |
| Aurora Global Database | 전용 복제 인프라로 여러 리전에 복제. 리전 간 지연 1초 미만 목표, 재해 복구·글로벌 읽기 |
[!WARNING] “Aurora가 빠르다”를 무조건 받아들이지 말자. 핵심은 네트워크 비용을 줄이는 로그 중심 설계가 당신의 워크로드(쓰기 패턴, 읽기 확장 요구, AZ 구성)와 맞는가다. 아키텍처를 이해하고 선택해야 한다 — 이 시리즈 전체가 말해온 그대로.
한눈에 보는 비교
지금까지의 차이를 표 하나로 정리하면 다음과 같다.
| 차원 | 전통 PostgreSQL | Amazon Aurora |
|---|---|---|
| 아키텍처 | 컴퓨트 = 스토리지 (단일 노드) | 컴퓨트 / 스토리지 분리 |
| 네트워크로 보내는 것 | WAL + 데이터 페이지(복제 시) | redo 로그 레코드만 |
| 데이터 페이지 쓰기 | 컴퓨트가 직접 | 스토리지가 비동기 구체화 |
full_page_writes/더블라이트 |
필요 | 불필요 |
| 체크포인트 | 있음 (튜닝 대상) | 없음 |
| 내구성 보장 | 로컬 fsync (+ 동기 복제) | 3 AZ 6중 복제, 4/6 쿼럼 |
| 복제 방식 | standby가 WAL 재생, 별도 디스크 | 공유 스토리지, 리더는 재생 불필요 |
| 복제본 수 / 지연 | 제한적 / 수백 ms~초 | 최대 15개 / 보통 100ms 미만 |
| 크래시 복구 | 마지막 체크포인트부터 직렬 재생 | 스토리지 상시 적용, 재시작 빠름 |
| 합의 | 동기 복제 시 standby 응답 대기 | 2PC 없음, 쿼럼 + 단일 로그 순서 |
| 확장 | scale-up 중심 | 스토리지 자동 확장, 리더 수평 확장 |
같은 표를 거꾸로 읽으면, Aurora가 포기하거나 다르게 가져간 것들(완전 호환성, 비용 모델, 커밋 지연 특성)도 함께 드러난다. 어느 쪽이 “옳다”가 아니라, 무엇을 어디로 옮겼는지가 핵심이다.
정리
- 전통 PostgreSQL은 한 노드가 WAL·데이터 페이지·체크포인트·FPI를 모두 쓰고, standby가 WAL을 재생한다. 클라우드에서 이 모든 쓰기가 네트워크 비용으로 돌아온다.
- Aurora의 두 기둥은 스토리지-컴퓨트 분리와 “The log is the database” — 컴퓨트는 redo 로그만 보내고, 페이지는 스토리지가 구체화한다.
- 그 결과 컴퓨트의 쓰기 증폭이 사라진다. 논문 벤치마크 기준 미러링 MySQL 대비 약 35배 트랜잭션, 노드 I/O는 트랜잭션당 약 7.7배 감소.
- 스토리지는 3 AZ에 6중 복제 + 10GB 세그먼트, 쓰기 4/6·읽기 3/6 쿼럼으로 AZ+1 내결함성과 자가치유를 얻는다.
- 복제는 공유 스토리지 기반이라 read replica를 최대 15개, 보통 100ms 미만 lag으로 붙인다 — standby가 재생하던 Part 5 방식과 근본적으로 다르다.
- 체크포인트가 없고, redo가 스토리지에서 상시 적용되므로 크래시 복구가 빠르다 — Part 1·2의 체크포인트·REDO와 대비된다.
- 대신 완전한 PostgreSQL은 아니며, 쓰기 지연 특성·비용·워크로드 적합성을 따져야 한다.
이 시리즈를 관통하는 결론은 하나다 — 내부 동작을 알아야 트레이드오프가 보이고, 트레이드오프가 보여야 옳은 선택을 한다. Aurora조차도 결국 WAL이라는 한 줄기 로그를 어떻게 다룰지 다시 설계한 이야기였다. 그리고 같은 문제를 오픈소스로 다르게 푼 답이 하나 더 있다 — 다음 편에서 Neon을 본다.
관련 포스트
- PostgreSQL WAL과 크래시 복구 — Part 2. Aurora가 뒤집은 redo/체크포인트의 원형
- PostgreSQL 복제와 고가용성(HA) — Part 5. 공유 스토리지 복제와 대비되는 전통 스트리밍 복제
- PostgreSQL MVCC와 VACUUM 내부 동작 — Part 1. 체크포인트·정리 작업의 I/O 맥락
- PostgreSQL 커넥션과 메모리 구조 — Part 6. 컴퓨트 노드의 리소스 모델
- MySQL vs PostgreSQL — 백엔드 개발자가 알아야 할 차이 — 오픈소스 두 DB의 비교