[PostgreSQL] 읽기 부하를 분산하고 싶을 때 — 스트리밍 복제와 replica
설치·접속: PostgreSQL 설치와 접속
부제: 이 서버가 주(primary)인지 대기(standby)인지, 복제가 붙어 있는지, 복제 슬롯은 무엇인지부터 확인한다
-- 지금 이 서버의 역할: 복구(대기) 모드인가?
SELECT pg_is_in_recovery(); -- f = primary, t = standby
-- 붙어 있는 스탠바이 목록
SELECT * FROM pg_stat_replication;
-- 복제 설정
SELECT name, setting FROM pg_settings
WHERE name IN ('wal_level','max_wal_senders','hot_standby');
pg_is_in_recovery
-------------------
f -- 이 서버는 primary
(0 rows) -- pg_stat_replication: 아직 붙은 스탠바이 없음
name | setting
------------------+---------
hot_standby | on
max_wal_senders | 10
wal_level | replica -- 복제 가능 상태는 갖춰져 있다스트리밍 복제의 뼈대는 이렇다. Primary는 자기 변경분(WAL)을 실시간으로 스탠바이에 흘려보내고, 스탠바이는 그걸 계속 재생해 primary와 거의 같은 상태를 유지한다. pg_is_in_recovery()가 f니 이 서버는 primary고, wal_level=replica에 max_wal_senders=10이라 복제를 받을 준비는 됐지만 pg_stat_replication이 비어 있으니 아직 붙은 스탠바이는 없다. 스탠바이(hot_standby=on)에서는 읽기 쿼리를 받을 수 있어서, 무거운 조회를 스탠바이로 돌려 primary의 쓰기 부하와 분리하는 게 읽기 분산의 기본 그림이다.
스탠바이가 밀리지 않게 primary가 WAL을 함부로 지우지 않도록 잡아 두는 장치가 복제 슬롯이다. 아래처럼 슬롯을 만들면 그 스탠바이가 아직 안 받아간 WAL을 primary가 보존한다.
SELECT * FROM pg_create_physical_replication_slot('m8_standby_slot');
SELECT slot_name, slot_type, active, restart_lsn FROM pg_replication_slots;
SELECT pg_drop_replication_slot('m8_standby_slot'); -- 안 쓰면 삭제
slot_name | slot_type | active | restart_lsn
-----------------+-----------+--------+-------------
m8_standby_slot | physical | f |주의: 슬롯을 만들어 놓고 스탠바이가 안 붙으면(active=f) primary가 WAL을 계속 쌓아 디스크가 차오른다. 안 쓰는 슬롯은 반드시 지운다.
이렇게도 쓴다
스탠바이를 pg_basebackup으로 뜬다. (조합: -R 복구설정 자동생성)
pg_basebackup -h primary.internal -U replica_user \
-D /var/lib/postgresql/16/main -R -X stream -S m8_standby_slot
복제 전용 계정을 만든다. (조합: createuser --replication)
createuser --replication --pwprompt replica_user
스탠바이가 얼마나 밀렸는지(지연) primary에서 잰다. (조합: LSN 뺄셈)
SELECT client_addr, state,
pg_size_pretty(pg_wal_lsn_diff(sent_lsn, replay_lsn)) AS replay_lag
FROM pg_stat_replication;
스탠바이 쪽에서 재생 지연을 시간으로 본다. (조합: 스탠바이에서)
SELECT now() - pg_last_xact_replay_timestamp() AS replication_delay;
동기 복제로 바꿔 커밋을 스탠바이 반영까지 기다리게 한다. (조합: synchronous_standby_names)
synchronous_standby_names = 'm8_standby' # 데이터 유실 0, 대신 지연 증가
primary가 죽었을 때 스탠바이를 승격시킨다. (조합: pg_ctl promote)
pg_ctl promote -D /var/lib/postgresql/16/main