DB Study

10장. Replication — 한 대가 죽어도 서비스는 계속된다

9장의 WAL은 복구용 일기장이자, 복제의 방송 원본이기도 합니다. WAL 스트리밍이 스탠바이로 흐르는 길, 동기/비동기의 트레이드오프, 그리고 페일오버의 순간까지.

고급 ⏱ 약 35분 🎬 애니메이션 3개 선수 지식: 9장 WAL
🧭
이 장에서 배우는 것

① Physical vs Logical — 두 종류의 복제와 선택 기준 ② WAL 스트리밍이 스탠바이로 흐르고 적용되는 과정(write/flush/replay lag) ③ 동기/비동기 커밋의 차이와 쿼럼 구성 ④ 페일오버 시나리오와 replication slot의 빛과 그림자

10.1 두 종류의 복제 — Physical vs Logical

PostgreSQL의 복제는 크게 두 갈래입니다. Physical 복제는 WAL 바이트를 그대로 스트리밍해 "클러스터 전체의 완전한 쌍둥이"를 만들고, Logical 복제는 WAL을 해석(디코드)해 "행 단위 변경"만 골라 보냅니다.

항목Physical ReplicationLogical Replication
복제 단위WAL 바이트 (블록·레코드)논리 row 변경 (INSERT/UPDATE/DELETE)
스코프클러스터 전체 (모든 DB·객체)지정한 PUBLICATION (특정 테이블)
스탠바이 쓰기불가 (read-only)가능 (독립 primary)
DDL 복제자동으로 함께안 됨 (v17까지) — 수동 적용
메이저 버전동일해야 함서로 달라도 됨 → 업그레이드에 활용
주 용도HA, 읽기 분산, DRCDC, 마이그레이션, 부분 복제

선택 기준은 간단합니다: "전체를 똑같이, 장애 시 즉시 전환" → Physical, "특정 테이블만, 다른 버전/스키마로" → Logical. 이 장은 HA의 근간인 Physical 스트리밍을 중심으로 봅니다.

10.2 WAL 스트리밍 — 커밋이 스탠바이에 닿기까지

Primary에서 커밋된 WAL은 walsender 프로세스가 네트워크로 흘려보내고, 스탠바이의 walreceiver가 받아 저장하고, startup 프로세스가 리플레이합니다. 각 단계마다 지연(lag)이 측정됩니다.

🐘 Primary pg_wal/ (commit LSN) WAL 기록 완료 walsender 스트리밍 담당 🛟 Standby (hot_standby=on) walreceiver — WAL 수신·write OS 버퍼에 기록 pg_wal/ fsync — 디스크 영속 startup — WAL 리플레이 읽기 쿼리에 보이게 됨 네트워크 스트리밍 ← 여기까지 write_lag ← 여기까지 flush_lag ← 여기까지 replay_lag pg_stat_replication write_lag ≤ flush_lag ≤ replay_lag 읽기 쿼리에 보이는 지연 = replay_lag
-- Primary에서: 스탠바이별 지연을 단계별로 분해
SELECT application_name, state, sync_state,
       pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn) AS replay_bytes,
       write_lag, flush_lag, replay_lag
FROM pg_stat_replication;

10.3 구축 레시피 — 3줄 요약

스트리밍 복제 구축은 v12+ 기준으로 매우 단순해졌습니다. Primary에 복제 권한을 열고, pg_basebackup 한 방으로 스탠바이를 만듭니다.

# [Primary] postgresql.conf
wal_level = replica              # v10+ 기본값
max_wal_senders = 10             # 스탠바이 수 + pg_basebackup 여유
max_slot_wal_keep_size = 64GB    # v13+. 슬롯 폭주 방어 (필수급 권장)

# [Primary] pg_hba.conf — 복제 접속 허용
host  replication  replicator  10.0.0.0/16  scram-sha-256

# [Standby] 데이터 디렉터리를 통째로 복제 + 자동 설정
pg_basebackup -h primary.example -U replicator \
    -D $PGDATA -X stream -R -C -S standby1_slot -P
# -R: standby.signal + primary_conninfo 자동 생성
# -C -S: replication slot 생성 후 연결
💡
Replication Slot — 약속 장치의 양면

슬롯은 "스탠바이가 아직 안 받아간 WAL은 지우지 않겠다"는 약속입니다. 일시적 단절에도 재동기화 없이 catch-up할 수 있어 강력하지만, 스탠바이를 폐기하고 슬롯만 남기면 WAL이 무한 축적되어 디스크가 터집니다. max_slot_wal_keep_size(v13+)로 상한을 꼭 두세요.

10.4 동기 vs 비동기 — 커밋은 누구를 기다리는가

기본 스트리밍은 비동기입니다. Primary는 자기 WAL만 flush하면 커밋을 응답하고, 스탠바이 전송은 뒤따라갑니다. 빠르지만, Primary가 그 직후 죽으면 마지막 몇 개의 커밋이 스탠바이에 없을 수 있습니다. synchronous_standby_names를 설정하면 동기가 되어 스탠바이의 확인까지 기다립니다.

⚡ 비동기 (기본) Primary Standby 로컬 WAL fsync ✓ 즉시 COMMIT 응답! 전송은 뒤따라감 💥 직후 Primary 사망하면? 마지막 커밋 몇 개가 스탠바이에 없을 수 있음 🔒 동기 (synchronous_standby_names) Primary Standby WAL 전송 flush 확인 ACK ACK 받은 후에야 응답 두 서버에 영속 = 손실 0 (RPO 0) ⚠️ 대가: 유일한 sync 스탠바이가 죽으면 Primary의 모든 커밋이 무한 대기! → ANY 2 (s1, s2, s3) 쿼럼 권장
# postgresql.conf — 쿼럼 기반 동기 복제
synchronous_standby_names = 'ANY 2 (s1, s2, s3)'  # 3대 중 아무 2대가 ACK하면 커밋

-- 정말 중요한 트랜잭션만 스탠바이 리플레이까지 기다리기
SET LOCAL synchronous_commit = remote_apply;   -- "쓴 직후 스탠바이에서 읽어도 최신" 보장

10.5 페일오버 — 스탠바이가 왕좌에 오르는 순간

계획된 전환은 Switchover, 장애에 의한 전환은 Failover라고 부릅니다. 핵심 절차는 같습니다: 스탠바이를 승격(promote)시키고, 트래픽을 돌리고, 구 Primary가 복귀할 땐 pg_rewind로 새 타임라인에 맞춥니다.

Application 🐘 Primary Timeline 1 🛟 Standby WAL 리플레이 중 쓰기 WAL 스트리밍 💥 장애! 스트리밍 끊김 👑 New Primary pg_ctl promote 쓰기 가능 상태로 전환! 쓰기 트래픽 전환 (DNS/VIP) Timeline 2 시작 🔀 구 Primary 부활 pg_rewind 후 Standby로 Timeline 2에 맞춰 되감기 → 역방향 복제 ⚠️ 그냥 켜면 Split-brain!
⚠️
수동 페일오버는 새벽 2시의 실수를 부른다

운영에서는 Patroni(etcd 기반 리더 선출, 가장 널리 사용), repmgr, pg_auto_failover 같은 자동화 도구를 씁니다. 공통 요구사항은 Split-brain 방지, 타임라인 관리, DNS/VIP 전환입니다. 관리형 서비스(RDS 등)는 플랫폼이 대신해 줍니다.

10.6 Hot Standby의 딜레마 — 읽기 쿼리 vs 리플레이

스탠바이에서 읽기 쿼리를 받다 보면 충돌이 생깁니다. Primary에서 VACUUM으로 지워진 행을, 스탠바이의 장기 쿼리가 아직 보고 있다면? WAL 리플레이를 기다리게 하거나, 쿼리를 취소하거나 — 둘 중 하나입니다.

설정효과부작용
max_standby_streaming_delay = 30s리플레이를 늦춰 쿼리에 양보복제 지연 누적
hot_standby_feedback = on스탠바이의 oldest xmin을 Primary에 전달 → 필요한 튜플의 vacuum 보류Primary에 Bloat 누적 (8장)

정답은 워크로드에 따라 다릅니다: 분석 쿼리용 스탠바이는 feedback on + 긴 delay, 실시간 HA용 스탠바이는 그 반대로 설정합니다.

✍️ 이해도 체크

비동기 복제 환경에서 Primary가 갑자기 죽어 스탠바이를 승격했습니다. 어떤 위험이 있나요?
✅ 비동기 복제는 로컬 WAL flush만으로 커밋을 응답하므로, 전송 전에 Primary가 죽으면 그 커밋들은 스탠바이에 없습니다. 이 손실을 0으로 만들려면 동기 복제(synchronous_standby_names)가 필요하고, 대신 커밋 지연과 "sync 스탠바이 장애 시 커밋 정지" 리스크를 감수해야 합니다. 쿼럼(ANY N)이 균형점입니다.
스탠바이를 폐기했는데 Primary에 replication slot을 지우지 않고 남겨두면 어떻게 되나요?
✅ 슬롯은 "소비자가 확인한 지점까지만 WAL 삭제 허용"이라는 약속이므로, 소비자가 사라지면 WAL이 영원히 쌓입니다. pg_wal 폭증의 최빈 원인입니다. 안 쓰는 슬롯은 pg_drop_replication_slot()으로 즉시 제거하고, v13+에서는 max_slot_wal_keep_size로 상한을 걸어 Primary를 보호하세요.
📚
원문으로 더 깊이

이 장의 원문 문서: chapters/ch10_replication.md — Logical Replication(PUBLICATION/SUBSCRIPTION) 상세, Cascading 복제, 읽기 분산 패턴, 진단·운영 레시피가 담겨 있습니다.