6.5 노드 복구와 WAL 보존
failover 뒤 옛 primary를 복구하면 keeper는 monitor에서 demoted를 받습니다. 이어서 새 primary를 따라가는 standby로 전환합니다. 이때 사용하는 방법은 두 가지입니다. 먼저 pg_rewind로 갈라진 부분만 되돌립니다. 이 작업이 실패하면 pg_basebackup으로 PGDATA 전체를 다시 받습니다. 두 방법의 비용 차이는 데이터 크기에 비례합니다. pg_rewind와 기존 standby의 streaming 재개에는 갈라진 지점 이후의 WAL이 남아 있어야 합니다. 새로 실행하는 pg_basebackup은 --wal-method=stream으로 필요한 WAL을 함께 받습니다. 따라서 과거 WAL 보존에 의존하지 않습니다. 이 장에서는 재합류 경로와 WAL 보존 장치를 함께 다룹니다.
재합류 경로
v2.2 소스(fsm_transition.c의 fsm_rewind_or_init)의 주석은 이 순서를 그대로 설명합니다. "First, try to rewind. If that fails, do a pg_basebackup." 실습 로그에도 같은 문장이 기록되었습니다.
05:10:42 10 INFO FSM transition from "demoted" to "catchingup": A new primary is available. First, try to rewind. If that fails, do a pg_basebackup.
05:10:42 10 INFO Postgres needs to enter crash recovery before pg_rewind.
docker kill로 멈춘 옛 primary에는 정상 종료 기록이 없습니다. 따라서 pg_rewind 전에 crash recovery를 먼저 거칩니다. 이 단계는 v1.6.1에 도입되었습니다.
pg_rewind가 실패한 사례
실습에서는 노드를 --dbname appdb로 생성했습니다. 그러나 첫 재합류에서 pg_rewind가 실패해 pg_basebackup으로 넘어갔습니다.
05:29:03 11 INFO pg_rewind: error: connection to server at "node1" (172.28.0.11), port 5432 failed: FATAL: no pg_hba.conf entry for host "172.28.0.12", user "pgautofailover_replicator", database "postgres", SSL encryption
05:29:03 11 ERROR Failed to run pg_rewind: exit code 1
05:29:03 11 WARN Failed to rewind demoted primary to standby, trying pg_basebackup instead
v2.2의 pgctl.c는 pg_rewind의 source server 연결에 데이터베이스 이름 postgres를 고정하여 전달합니다. 반면 pg_autoctl이 만드는 HBA 규칙은 --dbname으로 지정한 데이터베이스(appdb)와 replication만 허용합니다. 이 때문에 pg_rewind 접속이 거부되었습니다.
실습 데이터는 30 MB라 basebackup이 2초 만에 끝났습니다. 데이터가 수백 GB라면 pg_rewind로 끝날 작업이 전체 복사로 바뀝니다. 그동안 이 노드는 failover 후보가 아닙니다. 각 노드의 pg_hba.conf에 postgres 데이터베이스용 규칙을 추가하자 pg_rewind가 성공했습니다.
hostssl "postgres" "pgautofailover_replicator" 172.28.0.0/24 trust
05:30:24 10 INFO pg_rewind: connected to server
05:30:24 10 INFO pg_rewind: servers diverged at WAL location 0/7004D40 on timeline 3
05:30:24 10 INFO pg_rewind: no rewind required
네트워크 분리 실습에서 고립되었던 primary가 돌아왔을 때는 실제로 되돌릴 부분이 있었습니다.
05:37:58 11 INFO pg_rewind: servers diverged at WAL location 0/9026A30 on timeline 6
05:37:59 11 INFO pg_rewind: need to copy 180 MB (total source directory size is 206 MB)
05:38:01 11 INFO pg_rewind: Done!
실습 파일 scripts/allow-rewind.sh가 이 규칙을 추가합니다. 운영에서는 trust 대신 실제 인증 방식을 사용합니다.
pg_rewind 실패를 단순한 성능 저하로만 보면 안 됩니다. 대체 경로인 pg_basebackup은 PGDATA를 새로 받아 교체합니다. 백업을 받는 동안 디스크 공간이 두 배로 필요합니다(replication.backup_directory). 네트워크 속도는 replication.maximum_backup_rate(기본 100M)로 제한됩니다. --dbname에 postgres가 아닌 값을 사용한다면 설치 직후 HBA 규칙을 먼저 점검합니다.
pg_rewind의 전제 조건
pg_rewind는 wal_log_hints = on이거나 data checksum이 활성화되어 있어야 동작합니다. pg_autoctl은 postgresql-auto-failover.conf에 wal_log_hints = on을 기록합니다. 실습 노드의 값은 다음과 같았습니다.
wal_log_hints|on
data_checksums|off
checkpoint_timeout|300
wal_keep_size|8192
replication slot
pg_autoctl은 노드마다 pgautofailover_standby_<node id> 이름의 physical replication slot을 만듭니다. primary뿐만 아니라 standby도 다른 노드용 slot을 유지합니다. standby의 slot 유지 기능은 PostgreSQL 13 이상에서 동작합니다. 이는 v2.2 지원 범위 전체에 해당합니다. 소스는 11.9 미만과 12.4 미만에서 이 기능을 끕니다. v2.2 keeper.c의 주석은 그 이유를 "모든 노드가 다른 노드에게 필요할 수 있는 WAL 파일의 로컬 사본을 유지하게 하려는 것"이라고 설명합니다. 옛 primary가 standby로 재합류할 때 사용합니다. multi-standby failover에서 한 standby가 다른 standby로부터 WAL을 받아야 할 때도 사용합니다.
| 노드 역할 | slot 관리 방식 (v2.2 pgsql.c) |
|---|---|
| primary | 등록된 노드용 slot 생성, 삭제된 노드의 비활성 slot 제거. 전진 처리는 replication protocol이 담당 |
| standby | 다른 노드용 slot을 만들고 pg_replication_slot_advance()로 직접 전진 |
| catchingup | slot 유지 작업을 하지 않음 |
실습 로그에서도 standby가 되는 순간 slot을 생성하는 기록을 확인할 수 있습니다.
05:07:20 10 INFO Reached timeline 1, same as upstream node 1 "node1" (node1:5432)
05:07:20 10 INFO Creating replication slot "pgautofailover_standby_1"
WAL 보존과 디스크
pg_autoctl은 PostgreSQL 13 이상에서 wal_keep_size = '8 GB'도 설정합니다. slot과 별개로 primary는 항상 최근 8 GB의 WAL을 보존합니다. pg_wal 디스크를 이 크기보다 작게 할당하면 안 됩니다.
더 큰 위험은 slot입니다. primary의 slot 정리 쿼리는 monitor에서 삭제된 노드의 slot만 제거합니다. 노드가 중단되었더라도 monitor에 등록되어 있으면 해당 노드의 slot은 남습니다. 그러면 그 slot이 가리키는 위치 이후의 WAL이 계속 쌓입니다. v2.2 소스에서 max_slot_wal_keep_size를 설정하는 코드는 찾지 못했습니다. PostgreSQL 기본값(-1, 제한 없음)이 그대로 적용됩니다. 따라서 오랫동안 중단된 노드 하나가 primary의 pg_wal 디스크를 채울 수 있습니다.
-- primary에서 slot별 보존 WAL 크기
select slot_name, active, restart_lsn,
pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) as retained
from pg_replication_slots
order by restart_lsn;
대응 방법은 세 가지입니다. 노드가 돌아오지 않을 것이 확실하면 pg_autoctl drop node --force로 등록을 삭제해 slot이 정리되도록 합니다. 복구에 시간이 걸린다면 위 쿼리를 경보 항목으로 설정합니다. 디스크 보호가 우선이라면 max_slot_wal_keep_size를 직접 설정합니다. 이 경우 한도를 넘은 노드는 slot이 무효화됩니다. 따라서 pg_basebackup으로 재구축해야 합니다.
max_slot_wal_keep_size를 설정한 상태의 pg_auto_failover 동작은 이 노트에서 실습하지 않았습니다. 설정하기 전에 standby 재구축 시간과 디스크 여유를 함께 고려해야 합니다.
정리
재합류할 때는 pg_rewind를 먼저 시도하고 실패하면 pg_basebackup으로 대체합니다. --dbname을 변경한 설치에서는 pg_rewind가 HBA에 막혀 전체 복사로 넘어갑니다. 따라서 postgres 데이터베이스 규칙을 추가해 둡니다. WAL은 wal_keep_size 8 GB와 노드별 slot으로 보존됩니다. 등록된 상태로 중단된 노드의 slot이 디스크를 채우지 않도록 감시해야 합니다.