승격하기 전에 구 primary 가 정말 죽었는지 확인한다. 살아 있는 상태에서 standby 를 승격하면 쓰기를 받는 노드가 둘이 되어 데이터가 갈라진다. 되돌리려면 한쪽을 버려야 한다.
-- standby 에서
SELECT pg_is_in_recovery(); -- true 면 아직 standby
SELECT pg_last_wal_receive_lsn(), pg_last_wal_replay_lsn();
SELECT now() - pg_last_xact_replay_timestamp() AS replay_lag;
receive_lsn 과 replay_lsn 이 같아질 때까지 기다렸다가 승격하면 받은 WAL 을 모두 반영한 상태가 된다.
두 방법 중 아무거나 쓴다. 결과는 같다.
pg_ctl promote -D /var/lib/pgsql/17/data
SELECT pg_promote();
pg_promote() 는 기본으로 승격이 끝날 때까지 최대 60초 기다리고 성공 여부를 boolean 으로 돌려준다.
승격되면 서버가 standby.signal 파일을 스스로 지우고 읽기·쓰기 모드로 전환한다. 파일을 손으로 지우고 재시작하는 방식은 쓰지 않는다. 복구 중이던 WAL 을 마저 적용하지 못한 채 올라올 수 있다.
SELECT pg_is_in_recovery(); -- false 가 되어야 한다
버전별로 다른 점이 있다. PostgreSQL 11 이하는 recovery.conf 와 trigger_file 을 썼고, 12 부터 설정이 postgresql.conf 로 합쳐지고 standby.signal 파일이 그 역할을 한다. promote_trigger_file 설정은 16 에서 제거됐으므로 그 방식에 기대는 스크립트는 고쳐야 한다.
응용의 접속 대상을 새 primary 로 돌린다. 클라이언트 쪽에서 해결하려면 libpq 의 다중 호스트 기능이 간단하다.
postgresql://host1:5432,host2:5432/appdb?target_session_attrs=read-write
남은 standby 들은 새 primary 를 따라가도록 다시 물려야 한다. 타임라인이 갈라졌으므로 그냥 두면 붙지 않는다. pg_rewind 로 되돌리거나 pg_basebackup 으로 다시 만든다.
pg_rewind --target-pgdata=/var/lib/pgsql/17/data \
--source-server="host=newprimary user=repluser dbname=postgres"
pg_rewind 는 대상 클러스터에 wal_log_hints = on 이거나 data checksum 이 켜져 있어야 한다. 조건이 안 맞으면 처음부터 다시 복제한다.
수동 승격은 야간 장애에서 쓸 수 없다. 도구를 얹는다.
| 도구 | 성격 |
|---|---|
| repmgr | PostgreSQL 전용 CLI + repmgrd 데몬. 구조가 단순하다 |
| Patroni | etcd·Consul 같은 분산 합의 저장소를 쓴다. Kubernetes 와 궁합이 좋다 |
| pg_auto_failover | 모니터 노드가 상태를 판단한다 |
노드가 둘뿐이면 어떤 도구를 쓰든 split-brain 을 판정할 정족수가 없다. 증인 노드를 두거나 펜싱 수단(전원·스토리지 차단)을 함께 마련해야 한다.
dnf install -y repmgr_17
전용 롤과 데이터베이스를 만든다.
CREATE ROLE repmgr WITH LOGIN REPLICATION SUPERUSER PASSWORD '${PASSWORD}';
CREATE DATABASE repmgr OWNER repmgr;
primary 의 postgresql.conf 에 다음을 둔다.
shared_preload_libraries = 'repmgr'
wal_level = replica
max_wal_senders = 10
max_replication_slots = 10
hot_standby = on
wal_log_hints = on
pg_hba.conf 에 복제와 repmgr 데이터베이스 접근을 연다.
host replication repmgr 192.168.0.0/24 scram-sha-256
host repmgr repmgr 192.168.0.0/24 scram-sha-256
/etc/repmgr/17/repmgr.conf 는 노드마다 자기 정보를 적는다.
node_id=1
node_name='pg1'
conninfo='host=192.168.0.10 user=repmgr dbname=repmgr connect_timeout=2'
data_directory='/var/lib/pgsql/17/data'
failover='automatic'
promote_command='repmgr standby promote -f /etc/repmgr/17/repmgr.conf --log-to-file'
follow_command='repmgr standby follow -f /etc/repmgr/17/repmgr.conf --log-to-file --upstream-node-id=%n'
등록과 복제본 생성은 다음 순서다.
# primary 노드에서
repmgr -f /etc/repmgr/17/repmgr.conf primary register
# standby 노드에서 (데이터 디렉터리가 비어 있어야 한다)
repmgr -h 192.168.0.10 -U repmgr -d repmgr -f /etc/repmgr/17/repmgr.conf standby clone --dry-run
repmgr -h 192.168.0.10 -U repmgr -d repmgr -f /etc/repmgr/17/repmgr.conf standby clone
systemctl start postgresql-17
repmgr -f /etc/repmgr/17/repmgr.conf standby register
자동 전환은 양쪽 노드에서 repmgrd 를 띄워야 동작한다.
systemctl enable --now repmgrd
repmgr -f /etc/repmgr/17/repmgr.conf cluster show
no password supplied 는 standby clone 이나 repmgrd 가 비밀번호를 얻지 못한 것이다. conninfo 에 비밀번호를 적는 대신 postgres 계정의 ~/.pgpass 를 쓴다. 권한은 600 이어야 하고, 아니면 조용히 무시된다.
192.168.0.10:5432:repmgr:repmgr:${PASSWORD}
repmgr extension is available but not installed in database "repmgr" 는 확장이 아직 만들어지지 않은 상태다. primary register 가 확장을 만들어 주므로 그 단계를 건너뛰지 않았는지 본다. 직접 만들려면 repmgr 데이터베이스에서 CREATE EXTENSION repmgr; 을 실행한다.
각 명령을 어느 노드에서 실행하는지 헷갈리기 쉽다. primary register 는 primary 에서, standby clone 과 standby register 는 standby 에서 실행한다.