LLM WikiAccess-protected knowledge portal
← 스터디 홈
164편 · 약 14분

PostgreSQL 19 WAIT FOR: 비동기 복제본에서 Read-Your-Writes 일관성 확보

요약

PostgreSQL은 읽기 부하를 비동기 복제본(async replica)으로 분산하는 아키텍처를 오랫동안 지원해 왔다. 그러나 비동기 복제본은 주 서버보다 약간 뒤처지기 때문에, 사용자가 방금 쓴 데이터를 즉시 복제본에서 읽으면 보이지 않는 경우가 생긴다. 이 Read-Your-Writes(RYW) 불일치 문제를 해결하기 위해 애플리케이션은 지금까지 임시 해결책(세션별 타임아웃, 쓰기 후 primary로 재라우팅)에 의존해 왔다. PostgreSQL 19 Beta 1(2026년 6월 4일 출시)이 도입한 WAIT FOR 명령은 이 문제를 데이터베이스 레이어에서 직접 해결하는 첫 번째 공식 메커니즘이다.


1. 문제: 비동기 복제의 일관성 갭

비동기 복제의 동작 방식

PostgreSQL의 스트리밍 복제는 기본적으로 비동기 모드다. 주 서버가 트랜잭션을 커밋하면 즉시 클라이언트에게 성공을 응답하고, WAL(Write-Ahead Log) 레코드는 별도 스트림으로 복제본에 전달된다. 복제본의 WAL 리시버가 WAL을 받아 적용(replay)하는 데는 수 밀리초에서 수백 밀리초의 지연이 있다.

Primary
t=0: INSERT user_id=42 → 즉시 OK
LSN: 0/1ABC000
Replica
t=0: LSN 0/1AA0000 적용 중
user_id=42 아직 없음!
App
t=0: 쓰기 → primary
t=0+: 읽기 → replica
→ "내 데이터가 없어요!"
Read-Your-Writes 불일치: 비동기 복제의 구조적 특성
비동기 복제에서의 RYW 문제

기존 해결책의 한계

방법동작문제점
쓰기 후 primary로 재라우팅일정 시간 또는 세션 전체를 primary에서 읽기primary 읽기 부하 증가, 복제본 효과 반감
동기 복제synchronous_commit = on쓰기 레이턴시 급증, 복제본 장애 시 쓰기 블로킹
pg_sleep + 재시도쓰기 후 일정 시간 대기레이턴시 낭비, 복제 지연이 가변적이라 신뢰 불가
PgPool / Pgbouncer 라우팅 미들웨어LSN 기반 라우팅 직접 구현복잡성 높고 각 미들웨어마다 구현 방식이 달라 표준화 어려움

2. WAIT FOR 명령: 문법과 모드

기본 문법

WAIT FOR LSN 'lsn' [ WITH ( option [, ...] ) ]

옵션:

WAIT FOR LSN '0/1ABC000' WITH (
  MODE 'standby_replay',   -- 적용 모드 (기본값)
  TIMEOUT '5000',          -- 최대 대기 밀리초 (0 = 무제한)
  NO_THROW                 -- 타임아웃 시 오류 대신 false 반환
)

네 가지 모드

Primary ① 트랜잭션 커밋 WAL 생성 ② primary_flush WAL flush (fsync) ← primary_flush 대기 완료 WAL stream Standby (복제본) WAL 수신·write ← standby_write WAL flush (fsync) ← standby_flush WAL replay (적용) ← standby_replay (기본값) 모드 대기 조건 사용 목적 primary_flush 주 서버 WAL fsync 완료 주 서버 내구성 확인 standby_write 복제본 OS 버퍼 write 빠른 확인, 약한 내구성 standby_flush 복제본 WAL fsync 복제본 내구성 확인
WAIT FOR 모드별 대기 조건 비교

standby_replay (기본값)가 RYW 일관성에 직접 해당하는 모드다. 복제본이 해당 LSN까지 WAL을 적용(replay)한 후에야 쿼리를 실행한다. 이 모드는 standby 서버에서만 사용 가능하다.

primary_flush 는 primary 서버에서만 사용하며, 특정 LSN까지 WAL이 durable하게 저장됐는지 확인하는 용도다.


3. 실전 사용 패턴

패턴 1: 쓰기 후 복제본 읽기 보장

-- Primary에서 쓰기 수행
INSERT INTO orders (user_id, amount) VALUES (42, 9900);

-- 쓰기 시점의 LSN 수집
SELECT pg_current_wal_lsn() AS write_lsn;
-- 예: 0/1ABC000

-- ─────────────────────────────────────────────
-- Replica에 연결 후:
-- 해당 LSN이 replay될 때까지 대기 (최대 3초)
WAIT FOR LSN '0/1ABC000' WITH (
  MODE 'standby_replay',
  TIMEOUT '3000',
  NO_THROW
);

-- 이제 방금 쓴 데이터를 안전하게 읽을 수 있음
SELECT * FROM orders WHERE user_id = 42;

NO_THROW 옵션을 쓰면 타임아웃 시 오류 대신 false를 반환하므로, 애플리케이션이 직접 처리 흐름을 제어할 수 있다.

패턴 2: 미들웨어(Pgpool, HAProxy) 통합

-- 미들웨어가 LSN을 쿠키/헤더에 담아 복제본 연결 시 자동 실행
-- (PgBouncer init_query 또는 애플리케이션 커넥션 훅)
WAIT FOR LSN :'client_lsn' WITH (MODE 'standby_replay', TIMEOUT '2000', NO_THROW);

세션 변수나 애플리케이션 파라미터로 LSN을 전달하면, 미들웨어 레이어에서 일관성 보장을 투명하게 처리할 수 있다.

패턴 3: 함수로 추상화

CREATE OR REPLACE FUNCTION wait_for_replica(lsn pg_lsn, timeout_ms int DEFAULT 2000)
RETURNS bool AS $
BEGIN
  WAIT FOR LSN lsn WITH (MODE 'standby_replay', TIMEOUT timeout_ms::text, NO_THROW);
  RETURN true;
EXCEPTION WHEN OTHERS THEN
  RETURN false;
END;
$ LANGUAGE plpgsql;

4. 구현 내부: WAL replay 알림 메커니즘

복제본의 WAL replay 프로세스(startup process)는 WAL 레코드를 하나씩 적용할 때마다 내부 공유 메모리의 walRcvWrittenLSNreplayLSN을 갱신한다. WAIT FOR를 실행한 세션은 이 값을 폴링하거나 조건 변수(condition variable)를 통해 알림을 받는다. CPU 스핀 없이 latch 기반으로 대기하므로, 다수의 세션이 동시에 WAIT FOR를 실행해도 replay 프로세스에 영향을 주지 않는다.

클라이언트 세션
WAIT FOR LSN 실행
공유 메모리
replayLSN 상태
latch 대기
(CPU 스핀 없음)
WAL replay 프로세스
레코드 적용 시마다
replayLSN 업데이트
WAIT FOR 내부 동작 흐름

5. 제한 사항과 트레이드오프

복제 지연이 크면 대기 시간도 길어진다

WAIT FOR는 마법이 아니다. 복제본이 실제로 지연된 만큼 기다린다. 복제 지연(replication lag)이 수 초에 달하는 상황에서 WAIT FOR를 남용하면 읽기 레이턴시가 쓰기 레이턴시보다 길어진다.

적합한 환경: 복제 지연이 < 100 ms 인 안정적 클러스터

부적합한 환경: 복제 지연이 크거나 가변적인 환경 (네트워크 불안정, 복제본 고부하)

TIMEOUT 설정 지침

TIMEOUT = max(예상 복제 지연 × 3, 500ms)

타임아웃이 너무 짧으면 NO_THROW 없이는 오류가 발생하고, 너무 길면 사용자 경험에 악영향을 준다. NO_THROW와 함께 사용하고, 타임아웃 발생 시 primary로 재라우팅하는 폴백을 두는 것이 권장 패턴이다.

standby 전용 모드의 제약

standby_replay, standby_write, standby_flush복제본(recovery 모드)에서만 동작한다. Primary 서버에서 실행하면 오류가 발생한다. 애플리케이션이 연결을 primary/replica로 구분하지 않는 경우 주의가 필요하다.


6. 기존 솔루션과의 비교

특성WAIT FOR (PG 19)pg_last_xact_replay_timestamp 폴링동기 복제 (synchronous_commit)Citus / 분산 DB
구현 위치DB 내부애플리케이션DB 내부플랫폼
쓰기 레이턴시 영향없음없음있음 (복제본 응답 대기)있음
읽기 레이턴시복제 지연만큼 추가폴링 간격만큼 추가추가 없음다양
복제본 장애 시타임아웃 후 폴백애플리케이션 처리쓰기 블로킹다양
표준화SQL 표준 (PG 19+)비표준SQL 표준플랫폼 의존

7. 운영 체크리스트

  • [ ] PostgreSQL 버전 확인: 19 Beta 1+ 필요 (2026년 6월 4일 이후); GA 릴리스는 2026년 말 예상
  • [ ] 복제 지연 모니터링: pg_stat_replication.replay_lag을 Prometheus exporter로 수집
  • [ ] TIMEOUT 값 결정: replay_lag p99 × 2 를 기본값으로, 애플리케이션 SLO에 맞게 조정
  • [ ] NO_THROW + 폴백 구현: 타임아웃 발생 시 primary로 재라우팅하는 코드 경로 확보
  • [ ] standby 전용 코드 경로: primary 연결에 WAIT FOR standby_* 모드가 실행되지 않도록 Guard 추가
  • [ ] 커넥션 풀 통합: pgBouncer server_reset_query나 Pgpool session_init_query로 LSN 자동 설정 여부 검토
  • [ ] 부하 테스트: WAIT FOR 대기 중 복제본 부하 측정 (latch 기반이므로 CPU 부담은 낮음)

References