본문으로 건너뛰기
12.3 Hot standby와 query conflict

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-only

standby에서 가능한 작업:

작업가능
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원인의미
Snapshotvacuum이 dead tuple을 정리 — standby의 옛 스냅샷 무효화
Tablespacetablespace drop
LockDDL이 강한 락 잡음
Buffer pinbuffer cleanup 충돌
DatabaseDB 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 진행
30s30초 기다린 후에도 안 끝나면 취소
-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_slotsxmin 잡힘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 + 짧은 delayquery cancel 폭주
부하 분산 + hot_standby_feedback = onprimary BLOAT 폭증
max_standby_streaming_delay = -1standby 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을 봅니다.