12.3 Hot standby와 query conflict
Hot standby는 standby에서도 SELECT를 받을 수 있게 하는 기능. 같은 클러스터에서 read 부하를 분산하거나 분석용 standby를 두는 표준 방법입니다. 단, standby에서 query를 돌리는 동안 primary가 WAL을 적용하려 들면 conflict가 생깁니다. 이 동작을 이해해야 운영이 안정됩니다.
활성
# standby의 postgresql.conf
hot_standby = on # 기본 on (PG 10+)hot_standby = on인 standby는 recovery 중에도 SELECT를 받습니다.
사용
psql -h standby.example.com -d app_main
SELECT count(*) FROM orders; # OK
INSERT INTO orders ...; # ERROR — read-onlystandby에서 가능한 작업:
| 작업 | 가능 |
|---|---|
| SELECT (read-only, read-write 트랜잭션 제외) | 가능 |
SET TRANSACTION READ ONLY 명시 | 가능 |
| temporary table | 가능 |
LISTEN/NOTIFY | 일부 |
sequence nextval | 불가 |
| 모든 쓰기 SQL | 불가 |
CREATE/DROP/ALTER | 불가 |
Query conflict 5가지
standby에서 SELECT 중 primary의 WAL이 도착해 그 SELECT가 보고 있는 row를 건드리려 할 때 충돌이 발생합니다.
| Conflict | 원인 | 의미 |
|---|---|---|
| Snapshot | vacuum이 dead tuple을 정리 — standby의 옛 스냅샷 무효화 | |
| Tablespace | tablespace drop | |
| Lock | DDL이 강한 락 잡음 | |
| Buffer pin | buffer cleanup 충돌 | |
| Database | DB drop |
가장 자주 만나는 건 Snapshot conflict — primary의 VACUUM이 dead tuple을 cleanup하면 standby의 옛 스냅샷이 깨집니다.
충돌 해결 — max_standby_*
standby가 충돌 시 얼마나 기다릴지 결정합니다.
max_standby_archive_delay = 30s # archive로 받은 WAL
max_standby_streaming_delay = 30s # 스트리밍 WAL| 값 | 동작 |
|---|---|
0 | 즉시 query 취소 → recovery 진행 |
30s | 30초 기다린 후에도 안 끝나면 취소 |
-1 | 무한 — WAL 재생 멈춤 (lag 폭증 위험) |
운영 트레이드오프:
- 짧은 값: query 자주 cancel, 하지만 standby가 primary와 거의 동기
- 긴 값: query 성공률 ↑, standby lag 커질 수 있음
보고서·분석 standby는 5min~10min, 읽기 부하 분산 standby는 30s 정도가 보통.
query cancel 에러
ERROR: canceling statement due to conflict with recovery
DETAIL: User query might have needed to see row versions that must be removed.애플리케이션이 이걸 잡아 재시도해야 합니다. 처음 standby에 read를 보내는 워크로드면 이 에러 핸들링이 필수입니다.
hot_standby_feedback
standby가 자기 트랜잭션의 xmin을 primary에 알려 vacuum이 그 dead tuple을 잡지 않게 막습니다.
# standby
hot_standby_feedback = on| 효과 | 메모 |
|---|---|
| query cancel ↓ | conflict 안 일어남 |
| primary BLOAT ↑ | standby의 긴 트랜잭션이 primary vacuum 막음 |
pg_replication_slots의 xmin 잡힘 | catalog_xmin도 |
트레이드오프: 표준 OLTP는 off, 분석 standby는 on + 별도 vacuum 튜닝.
시각화
%%{init: {'theme':'base','themeVariables':{
'primaryColor':'#ede9fe',
'primaryTextColor':'#3b0764',
'primaryBorderColor':'#6d28d9',
'lineColor':'#1d4ed8',
'secondaryColor':'#d1fae5',
'tertiaryColor':'#fef3c7',
'noteBkgColor':'#fef3c7',
'noteBorderColor':'#b45309',
'noteTextColor':'#78350f',
'actorBkg':'#dbeafe',
'actorBorder':'#1d4ed8',
'actorTextColor':'#1e3a8a',
'activationBkgColor':'#d1fae5',
'activationBorderColor':'#047857',
'signalColor':'#1d4ed8',
'signalTextColor':'#1e3a8a',
'labelBoxBkgColor':'#fef3c7',
'labelBoxBorderColor':'#b45309',
'labelTextColor':'#78350f'
}}}%%
sequenceDiagram
participant App as 분석 app
participant Standby
participant Primary
App->>Standby: SELECT (big query 시작)
Note over Standby: snapshot LSN = X 기록
Primary->>Primary: VACUUM이 dead tuple 정리
Primary->>Standby: WAL 도착 (cleanup record)
Note over Standby: 이 record를 적용하면<br/>App의 스냅샷 무효
Standby->>App: max_standby_streaming_delay 동안 대기
alt query 끝나면
Standby->>Standby: 무사 통과
else delay 초과
Standby->>App: ERROR: canceling statement
end
hot_standby_feedback 대안 — replication slot의 xmin
PG 14+의 hot_standby_feedback과 슬롯 조합:
-- standby의 슬롯
ALTER ROLE repl_user SET replication_slot_advance = ...;복잡 — 일반적으로는 hot_standby_feedback = on 또는 max_standby_*_delay 큰 값으로 해결합니다.
실시간 read 일관성
standby의 lag로 인해 방금 primary에 INSERT한 row를 standby에서 SELECT하면 안 보임. 비동기 복제의 자연 결과입니다.
-- primary
INSERT INTO orders ... ;
-- (밀리초 후)
-- standby
SELECT * FROM orders WHERE ...; -- 안 보일 수 있음해결:
synchronous_commit = remote_apply+ 동기 복제 (비싼)- 애플리케이션이 쓰기 직후는 primary에서 read
pg_wait_for_lsn같은 기법 (PG 12 이전엔 함수 없음)
PG 13+의 pg_wait_for_lsn(lsn)은 standby가 LSN 도달까지 대기 — read after write 패턴 구현 가능합니다.
standby promote
primary 사고 시 standby를 read-write로 승격:
# standby에서
pg_ctl -D /var/lib/pgsql/17/data promote또는 SQL (PG 12+):
SELECT pg_promote();promote 후 standby는 새 timeline의 primary가 됩니다. 다른 standby들을 새 primary로 다시 연결 — 또는 자동화 도구(Patroni 등)에 위임.
운영 사례
분석용 standby
hot_standby = on
hot_standby_feedback = on
max_standby_streaming_delay = 10min긴 분석 쿼리 친화. primary BLOAT 모니터링 필수입니다.
읽기 부하 분산
hot_standby = on
hot_standby_feedback = off
max_standby_streaming_delay = 30s쿼리는 짧을 거니까 cancel 거의 없습니다. lag 작습니다. application은 cancel 에러 재시도.
운영 안티패턴
| 안티패턴 | 위험 |
|---|---|
분석용 + hot_standby_feedback = off + 짧은 delay | query cancel 폭주 |
부하 분산 + hot_standby_feedback = on | primary BLOAT 폭증 |
max_standby_streaming_delay = -1 | standby lag 무한 — 사실상 비활성 |
| application이 cancel 에러 안 잡음 | 사용자에게 에러 노출 |
| 한 standby로 분석·OLTP 모두 | 두 워크로드 충돌 |
정리
- hot standby = standby에서 read 가능
- 충돌 5종 중 가장 자주 보는 건 snapshot conflict (vacuum이 dead tuple 정리 시)
max_standby_*_delay로 query vs lag 트레이드오프 조절hot_standby_feedback은 cancel 줄이지만 primary BLOAT 위험- 분석 standby와 OLTP read standby는 다른 튜닝
- read-after-write는
pg_wait_for_lsn(PG 13+) 또는 primary로
다음 절(12.4)에서는 스키마 변경 + 일부 테이블만 복제 가능한 — logical replication을 봅니다.