백업 시리즈를 쓰면서 늘 걸리던 질문이 있었어요. Barman이든 pgBackRest든 결국 인스턴스 하나를 통째로 받는 구조인데, 데이터가 수십 TB를 넘어가면 이 모델은 어디까지 버틸까 하는 것이었죠.

PlanetScale이 7월 31일 공개한 글이 그 질문의 한 답을 보여줍니다. petabyte 규모 데이터베이스를 초당 50GB 넘는 속도로, 시간 단위 안에 백업하는 구조입니다. 전제가 하나 있는데, 데이터가 이미 shard로 나뉘어 있다는 것입니다.

단일 인스턴스의 산수

출발점은 단순한 산수입니다. 32TB 데이터베이스를 초당 500MB로 받으면 약 22시간이 걸립니다. 하루 두 번 백업(RPO 12시간)이 목표라면 22시간짜리 백업으로는 산수가 안 맞습니다. 백업이 끝나기 전에 다음 백업이 시작되어야 하니까요.

속도를 올리는 데는 한계가 있습니다. 백업 읽기가 빨라질수록 production 쿼리와 I/O를 다투게 되고, 네트워크와 스토리지 처리량 상한도 있습니다. 단일 인스턴스 모델에서는 데이터가 커질수록 백업 소요 시간이 선형으로 늘어나는 걸 피할 수 없습니다.

PlanetScale의 답은 분모를 늘리는 것입니다. 데이터가 8개 shard로 나뉘어 있으면, shard마다 백업을 동시에 받아서 32TB 전체가 약 2.8시간에 끝납니다. shard가 100개면 100TB도 같은 시간입니다. 백업 소요 시간이 전체 크기가 아니라 가장 큰 shard의 크기에 묶이는 구조입니다.

flowchart TD
    A[32TB 데이터
8개 shard] --> B[shard별 백업
전용 인스턴스 8대] B --> C[S3에 병렬 업로드] C --> D[전체 2.8시간
단일이면 22시간]

여기서 논쟁이 된 선택이 하나 있습니다. 백업을 primary나 기존 replica에서 받지 않고, 백업 때마다 shard별 전용 EC2 인스턴스를 새로 띄웁니다. production 쿼리에 백업 읽기 부하를 섞지 않겠다는 선택인데, Hacker News에서는 그 비용이 타당하냐는 반론이 붙었습니다. 클라우드에서 시간 단위로 인스턴스를 빌릴 수 있으니 가능한 설계이고, 백업 시간에만 존재하는 인스턴스라 상시 replica 한 대보다 쌀 수도 있습니다. 온프레미스에서는 흉내 내기 어려운 부분입니다.

복원의 하이브리드: WAL은 S3에서, 마지막 몇 분은 primary에서

이 글에서 제가 제일 재미있게 읽은 부분은 백업이 아니라 복원 쪽입니다. 백업 전용 인스턴스는 어떻게 최신 상태를 따라잡을까요.

절차는 이렇습니다. S3에서 직전 백업을 복원하고, 그 뒤의 WAL을 replay해서 따라잡습니다. WAL 대부분은 wal-g로 아카이빙된 S3에서 가져오는데, 여기에 구멍이 하나 있습니다. PostgreSQL은 완결된 WAL segment만 archive하므로, 지금 쓰이고 있는 segment의 내용은 S3에 아직 없습니다. archive_timeout을 5분으로 설정해도 최신 몇 분은 항상 S3 밖에 있습니다.

그래서 마지막 구간만 primary에서 직접 streaming으로 받습니다. S3 replay가 대역폭을 마음껏 쓰며 대부분을 처리하고, primary는 마지막 몇 분치만 감당하니 부하가 거의 없습니다.

구간출처primary 부하
베이스 백업S3없음
WAL 대부분S3 (wal-g archive)없음
마지막 몇 분primary streaming미미

restore_command와 streaming replication을 순서대로 조합하는 것 자체는 PostgreSQL 표준 기능입니다. standby가 archive recovery에서 streaming으로 넘어가는 그 메커니즘을 백업 인스턴스 따라잡기에 그대로 쓴 것인데, 표준 부품의 좋은 재조합입니다.

Barman/pgBackRest 세계에서 보면

제가 Barman 시리즈에서 다룬 도구들과 이 구조는 층이 다릅니다. Barman과 pgBackRest는 인스턴스 하나의 백업을 잘 받는 도구이고, PlanetScale의 구조는 그 위에서 “인스턴스가 아주 많고 각각이 작다"는 전제로 짠 오케스트레이션입니다. 실제로 부품은 익숙한 것들입니다. pg_basebackup으로 시드하고, wal-g로 아카이빙하고, S3에 쌓습니다.

그래서 이 글의 교훈을 일반 조직에 그대로 가져오긴 어렵습니다. shard가 없는 32TB 단일 클러스터라면 이 구조는 시작조차 못 합니다. 대신 두 가지는 규모와 무관하게 유효합니다.

하나, 복원 리허설의 산수를 미리 해 볼 것. 우리 클러스터 크기와 스토리지 속도로 복원이 몇 시간인지, RTO와 맞는지 계산해 보면 됩니다. 22시간이라는 숫자는 백업이 아니라 복원에서 먼저 문제가 됩니다.

둘, archive의 마지막 구멍을 인지할 것. archive_timeout이 있어도 최신 변경분은 archive에 없습니다. PITR 계획이 “archive에 다 있다"를 전제한다면 그 전제는 몇 분짜리 구멍을 갖고 있는 셈이고, 이 구멍을 메우는 것이 streaming이든 동기 standby든 별도 장치여야 합니다.

subtransaction 분석에 이어 PlanetScale의 PostgreSQL 엔지니어링 글이 연달아 좋네요. Vitess로 MySQL을 sharding하던 회사가 PostgreSQL에 같은 체급의 인프라를 짓는 과정이 실시간으로 공개되고 있으니, 당분간 이 블로그의 단골 출처가 될 것 같아요.

참고 자료