[PostgreSQL] primary가 죽었다 — standby를 pg_promote로 승격한다
설치·접속: PostgreSQL 설치와 접속
부제: primary 서버가 응답이 없고 애플리케이션이 전부 쓰기 실패를 뱉을 때, 대기 중이던 standby를 새 primary로 올려 서비스를 되살릴 때
-- 승격 직전: standby는 read-only 복구 모드다
SELECT pg_is_in_recovery(); -- t
SELECT timeline_id FROM pg_control_checkpoint(); -- 1
-- primary가 죽었다. standby를 새 primary로 올린다.
SELECT pg_promote();
-- 승격 후: 복구 모드 해제 + 타임라인 증가 + 쓰기 가능
SELECT pg_is_in_recovery(); -- f
CHECKPOINT;
SELECT timeline_id FROM pg_control_checkpoint(); -- 2
-- 승격 전
pg_is_in_recovery timeline_id
------------------- -------------
t 1
pg_promote
------------
t
-- 승격 후 (로그)
LOG: received promote request
LOG: selected new timeline ID: 2
pg_is_in_recovery timeline_id
------------------- -------------
f 2
-- 현재 WAL 파일명이 새 타임라인(00000002...)으로 넘어갔다
pg_walfile_name
--------------------------
000000020000000000000005standby는 평소 read-only 복구 모드로 primary의 WAL을 받아 계속 재생만 한다(pg_is_in_recovery()가 t). primary가 죽으면 이 standby를 승격시켜 쓰기를 받는 새 primary로 전환한다. SELECT pg_promote()(또는 셸에서 pg_ctl promote -D <datadir>)를 실행하면 서버가 복구를 끝내고 새 타임라인을 연다 — 위 실측에서 타임라인 ID가 1에서 2로 올라갔고 WAL 파일명도 00000002...로 바뀌었다. 타임라인이 갈라지는 이유는 "이 지점부터는 옛 primary가 아니라 내가 정본"이라고 분기를 선언하는 것이고, 이게 있어야 나중에 옛 primary가 살아나도 서로의 히스토리를 구분해 되붙일 수 있다. 승격 직후엔 pg_is_in_recovery()가 f가 되고 INSERT/UPDATE가 통과한다. 주의: 승격은 되돌릴 수 없다. 잘못 올리면 옛 primary와 새 primary 양쪽이 각자 쓰기를 받는 split-brain이 되므로, 승격 전에 옛 primary가 확실히 죽었는지(펜싱) 반드시 확인해야 한다. 그리고 애플리케이션이 새 primary 주소를 바라보도록 연결을 갈아끼우는 것까지가 failover다 — DB만 올린다고 트래픽이 자동으로 따라오지 않는다.
이렇게도 쓴다
셸에서 직접 승격한다. psql 접속이 안 되는 상황에서도 데이터 디렉토리만 있으면 올릴 수 있다.
pg_ctl promote -D /var/lib/postgresql/data
# server promoting
승격이 실제로 끝났는지 로그로 확인한다. promote request → selected new timeline이 보이면 전환 완료. (조합: 로그 확인)
grep -iE 'promote|new timeline' /var/lib/postgresql/data/log/*.log
# LOG: received promote request
# LOG: selected new timeline ID: 2
자동 failover가 필요하면 Patroni / repmgr를 얹는다. 위 과정은 전부 수동이다 — 사람이 primary 장애를 감지하고, 옛 primary를 펜싱하고, 승격하고, 앱 연결을 바꿔야 한다. 새벽 3시 장애에 이걸 사람이 하면 다운타임이 길어진다. Patroni(분산 합의로 리더 선출·자동 승격, HAProxy로 라우팅)나 repmgr(repmgrd 데몬이 감시·승격)는 이 감지→펜싱→승격→라우팅을 자동화한다. 노드 몇 대 이상으로 커지거나 무인 운영이 필요해지는 시점이 도입 기준이다.
개념: [failover 자동화 스택]
Patroni + etcd/Consul → 리더 선출·자동 승격, 분산 락으로 split-brain 방지
repmgr + repmgrd → 장애 감지 후 후보 standby 자동 승격
(앞단) HAProxy/pgBouncer → 클라이언트를 항상 현재 primary로 라우팅
승격 대상이 여러 standby일 때, 가장 덜 밀린(WAL을 많이 받은) 놈을 골라 데이터 손실을 최소화한다. (조합: LSN 비교)
-- 각 standby에서 실행해 replay 위치를 비교, 가장 앞선 노드를 승격 후보로
SELECT pg_last_wal_replay_lsn();
옛 primary를 버리지 말고 pg_rewind로 새 standby로 되붙인다. 승격으로 타임라인이 갈라졌기 때문에 옛 primary를 그냥 다시 붙이면 히스토리가 충돌한다. pg_rewind는 두 노드가 갈라진 지점 이후 바뀐 블록만 새 primary 기준으로 되감아, 전체 pg_basebackup 없이 옛 primary를 새 standby로 재편입시킨다. (조합: pg_rewind)
# 옛 primary를 정지한 뒤, 새 primary 기준으로 되감아 standby로 재활용
pg_rewind --target-pgdata=/var/lib/postgresql/data \
--source-server='host=new_primary user=postgres' -P
# 이후 standby.signal + primary_conninfo(=새 primary)로 기동하면 복제 재개