6.6 pg_rewind와 timeline
failover가 끝나도 클러스터의 일이 끝난 것은 아니다. 죽었던 구 primary는 언젠가 복구되어 돌아오는데, 그 노드를 새 primary의 replica로 되돌리는 과정에는 timeline 분기라는 걸림돌이 있다. Patroni는 이 재합류를 pg_rewind로 자동화하는 옵션을 제공하며, rewind가 불가능한 상황을 위한 처리 옵션도 별도로 둔다.
timeline은 왜 갈라지는가
PostgreSQL은 recovery를 마치고 promote되는 순간 새 timeline을 연다. failover에서 replica가 승격되면 클러스터의 WAL 이력은 그 시점에서 새 timeline으로 갈라진다. 문제는 구 primary 쪽이다. asynchronous replication에서는 primary가 커밋을 반환하고도 아직 replica로 전송하지 못한 WAL이 있을 수 있다. 이 구간은 승격된 새 primary에는 없고 구 primary의 옛 timeline에만 존재한다.
flowchart TD
A["timeline 1<br/>primary A 운영"] --> B["A 장애<br/>분기점 LSN X"]
B --> C["replica B promote<br/>timeline 2 시작"]
B --> D["A에만 있는 WAL<br/>LSN X 이후 구간"]
C --> E["클러스터는<br/>timeline 2로 진행"]
D --> F["forked timeline<br/>미복제 트랜잭션"]
E --> G["A 재합류하려면<br/>LSN X 이전으로 복귀"]
F --> G
공식 문서는 이 forked timeline에 남은 미복제 트랜잭션을 “effectively unrecoverable"이라고 표현한다. 승격 이후 클러스터는 이미 timeline 2에서 새 WAL을 쌓고 있으므로, 갈라진 가지의 트랜잭션을 본류로 합칠 방법이 없다. 유실 자체를 막으려면 사후 처리가 아니라 6.4 synchronous mode처럼 커밋 시점의 복제 보장이 필요하다.
구 primary 입장에서는 자신의 WAL이 분기점 LSN X를 지나 있으므로, 그대로는 새 primary의 streaming replica가 되지 못한다. 재합류하려면 데이터 디렉토리를 분기점 이전 상태로 되돌리거나, 처음부터 다시 복제해야 한다.
use_pg_rewind가 하는 일
postgresql.use_pg_rewind(기본 false)를 켜면, 구 leader가 replica로 클러스터에 재합류할 때 Patroni가 pg_rewind 실행을 시도한다. pg_rewind는 데이터 디렉토리 전체를 다시 복사하는 대신 새 primary와 달라진 블록만 되감으므로, 전체 재복제 대비 재합류 비용이 크게 줄어든다.
전제 조건이 있다. 공식 문서 원문 그대로, “Either the cluster must be initialized with data page checksums (--data-checksums option for initdb) and/or wal_log_hints must be set to on, or pg_rewind will not work.” 즉 클러스터를 data checksums로 초기화했거나 wal_log_hints가 on이어야 한다. 이미 운영 중인 클러스터는 checksums를 소급 적용하기 어려우므로, 부트스트랩 시점에 결정해 두는 편이 좋다.
# patroni.yml
bootstrap:
initdb:
- data-checksums # pg_rewind 전제 조건 확보
postgresql:
use_pg_rewind: true
authentication:
rewind:
username: rewind_user
password: rewind_passwordpostgresql.authentication.rewind는 rewind 전용 계정이다. PostgreSQL 11 이상에서 postgres 초기화 시 이 사용자가 생성되고 pg_rewind에 필요한 권한이 자동으로 부여되므로, superuser 계정 없이 rewind를 실행한다. 지정하지 않으면 superuser 자격이 사용된다.
rewind가 실패하거나 불가능할 때
rewind가 항상 성공하는 것은 아니다. Patroni는 실패 상황을 위한 처리 옵션 두 가지를 둔다. 둘 다 기본값은 false다.
| 옵션 | 발동 조건 | 동작 |
|---|---|---|
remove_data_directory_on_rewind_failure | pg_rewind 실행이 실패 | 데이터 디렉토리를 제거하고 replica를 재생성 |
remove_data_directory_on_diverged_timelines | timeline이 분기했는데 구 primary가 새 primary에서 streaming을 시작하지 못함 | 데이터 디렉토리를 제거하고 replica를 재생성 |
remove_data_directory_on_rewind_failure가 꺼져 있으면 rewind 실패 후에도 새 leader를 follow하려고 시도한다. remove_data_directory_on_diverged_timelines는 rewind를 쓰지 않거나 쓸 수 없는 클러스터에서 분기를 감지했을 때의 마지막 수단이다. 재생성은 Part V의 replica 생성 경로를 그대로 따른다.
timeline history 조회
failover와 switchover의 이력은 DCS의 history key에 누적되며, 두 가지 방법으로 조회한다. REST API의 GET /history는 pg_wal 디렉토리의 history 파일과 비슷한 형식으로 반환한다. 각 항목은 timeline 번호, 전환 지점 LSN, 사유, 시각의 4원소 배열이다.
curl -s http://localhost:8008/history | jq .[
[1, 25623960, "no recovery target specified", "2026-07-23T16:57:57+09:00"],
[2, 85662632, "no recovery target specified", "2026-07-24T09:12:04+09:00"]
]같은 정보를 CLI에서는 patronictl history로 본다. -f json처럼 출력 형식 지정도 지원한다.
patronictl history my-clusterDCS에 유지할 이력 개수는 max_timelines_history(기본 0)로 정하며, 0이면 전체 이력을 DCS에 보관한다.
patronictl history의 사유와 시각을 보면 언제 무엇 때문에 전환됐는지 역추적할 수 있다. 이전 primary와 timeline이 다른 노드의 승격을 막는 check_timeline 옵션은 6.2 후보 선정에서 다뤘다.정리
failover가 남기는 숙제는 구 primary의 재합류다. forked timeline의 미복제 트랜잭션은 되살릴 수 없으므로, 사후 처리의 목표는 유실 복구가 아니라 재합류 비용 절감이다. use_pg_rewind와 data checksums(또는 wal_log_hints)를 부트스트랩 시점에 준비해 두면 전체 재복제 없이 되감기로 합류하고, rewind가 실패하는 경우의 자동 재생성 여부는 두 remove_data_directory_* 옵션으로 정한다.