← 소프트웨어와 제품
심층 리포트DEEPER INTO TECHNOLOGY

PostgreSQL 19 WAIT: 저장 직후 복제본에서 옛 값이 보일 때

저장 직후 복제본이 옛 값을 반환할 때 PostgreSQL 19 WAIT를 조회 경로에 어떻게 넣어야 할까?

댓글 남기기 ↓
먼저 읽는 핵심

주문을 저장했다는 응답을 받았는데 다음 화면에는 주문이 없다. 쓰기는 주 서버로, 읽기는 비동기 복제본으로 나누면 생길 수 있는 일이다. PostgreSQL 19의 WAIT는 읽을 서버가 필요한 변경을 적용할 때까지 기다리는 명령이다. 이 기능을 쓰려면 무엇을 기다릴지뿐 아니라 어느 연결에서 읽고, 기다림이 끝나지 않으면 무엇을 보여 줄지도 정해야 한다.

주문 API가 성공을 반환한 직후, 화면이 주문 상세 API를 호출했다고 하자. 첫 요청은 주 서버에 기록했고 두 번째 요청은 읽기 부하를 나눠 받는 복제본으로 갔다. 복제본이 아직 그 변경을 적용하지 않았다면, 정상적인 두 요청 사이에서도 사용자는 “방금 만든 주문이 없다”는 응답을 받을 수 있다.

PostgreSQL의 물리 복제 설명은 비동기 복제에서 주 서버의 커밋과 복제본 조회에 변경이 보이는 시점 사이에 간격이 있음을 설명한다. 트랜잭션이 실패한 상황과 구별해야 한다. 이때 필요한 성질은 내가 완료한 쓰기를 이어지는 읽기에서 확인하는 것, 즉 read-your-writes다.

PostgreSQL 19에는 이를 위한 WAIT가 추가된다. 다만 2026년 10월 31일 기준 정식 출시 전이다. 10월 15일 RC 1 발표는 RC 2를 11월 12일, 정식 출시를 11월 19일로 계획한다. 일정은 변경될 수 있다. 이 글의 명령 설명은 10월 31일의 19 개발 문서 기준이며, RC 1 바이너리에서 모든 옵션을 직접 확인한 결과가 아니다. 아래 주문·시간 예는 자체 설계한 분석이고 DB 클러스터 실행이나 지연 측정은 하지 않았다.

기다려야 하는 것은 시간이 아니라 변경의 위치다

WAL은 DB 변경을 복구·복제하는 데 쓰이는 로그이고, LSN은 그 로그의 위치다. “0.5초 기다린 뒤 읽는다”는 방법은 그사이에 복제가 끝났다고 추측한다. 위치를 전달하는 방식은 “이 변경이 포함된 지점까지 적용됐는가”를 확인한다.

관리 함수 문서는 WAL의 삽입 위치, 쓰기 위치, 영구 저장 위치를 구분한다. pg_current_wal_insert_lsn()은 현재 삽입 위치를 반환한다. 이 값은 주문 번호나 그 주문만의 전용 토큰이 아니다. 서버 전체의 WAL 진행 위치이므로 다른 트랜잭션의 로그도 함께 포함할 수 있다.

공식 read-your-writes 절차는 쓰기 트랜잭션이 커밋된 뒤 주 서버에서 위치를 얻고, 이를 복제본에 전달하도록 설명한다. 복제본이 그 위치까지 재생했다면 필요한 커밋도 적용된 상태가 된다. 따라서 값을 얻는 순서가 중요하다. 커밋 전에 얻은 위치로는 아직 기록되지 않은 커밋까지 기다렸다고 말할 수 없다.

이를 주문 사례에 적용하면 쓰기 API의 책임이 한 가지 늘어난다. 주문 ID와 함께 “다음 읽기가 최소한 따라잡아야 할 위치”를 서버 쪽 요청 문맥에 남기는 것이다. 이 위치를 받은 조회 계층은 아무 복제본에서 즉시 읽는 대신, 실제 선택한 서버에서 조건을 확인한다. 위치가 누락된 요청을 어떤 일관성으로 처리할지도 별도 정책으로 정한다.

주 서버의 커밋 뒤 얻은 LSN을 복제본에 전달하고, 재생 대기의 성공을 확인한 후 같은 서버에서 조회하는 순서. 시간 초과는 조회 우회로 분기한다.

자체 제작한 설명도다. 실제 서버 추적이나 측정된 시간축이 아니다.

주문 상세를 읽는 경로를 하나로 묶는다

다음은 orders(id, status)에 대상 주문이 있다고 가정한 미실행 SQL 설계 예다. 주 서버의 명시적 트랜잭션에서 변경하고, 커밋 성공을 확인한 다음 위치를 얻는다. 쓰기와 위치 취득 사이에 다른 서버로 연결이 바뀌지 않는 상황을 전제로 한다.

-- 주 서버의 같은 연결: 대상 행과 변경 성공 여부도 확인해야 한다.
BEGIN;
UPDATE orders SET status = 'confirmed' WHERE id = 42;
COMMIT;
SELECT pg_current_wal_insert_lsn();

애플리케이션은 마지막 결과를 보관한다. 아래 0/500은 문법 설명용 임의 값이며, 실행할 때는 실제로 반환된 LSN으로 바꿔야 한다. 새 읽기 연결에서 먼저 대기하고, 반환 상태를 검사한다.

-- 조회할 복제본: 이전 트랜잭션이 남지 않은 연결에서 시작한다.
WAIT FOR LSN '0/500'
  WITH (MODE 'standby_replay', TIMEOUT '100ms', NO_THROW);
-- 애플리케이션이 status = 'success'를 확인한 경우에만 다음 조회 실행
-- 같은 물리 서버/연결을 유지한다.
SELECT id, status FROM orders WHERE id = 42;

두 문장을 상태 검사 없이 한 번에 보내는 예가 아니다. WAIT 명령의 NO_THROW는 timeout과 not in recovery를 반환 상태로 다루게 하므로 애플리케이션의 분기가 필요하다. 다른 오류까지 없애지는 않는다. TIMEOUT 생략이나 0은 무제한 대기다. 위 100ms는 사례의 선택값이며 공식 권장값이 아니다.

여기서 연결 풀의 동작이 결과를 좌우한다. 복제본 A에서 대기에 성공한 뒤 풀에 연결을 반환하고, 복제본 B에서 주문을 조회하면 A에서 확인한 사실을 B에 적용한 셈이다. 대기와 조회를 같은 물리 서버에 묶는 규약이 필요하다. 단순히 같은 읽기용 호스트 이름을 사용했다는 기록으로 충분한지, 프록시가 문장별로 목적지를 바꾸는지 확인한다. 이는 공식 절차를 풀 환경에 적용한 설계 판단이다.

또한 WAIT는 최상위 명령이며, 스냅샷을 보유한 상태에서 실행할 수 없다. 아직 목표에 도달하지 못한 standby 대기는 보유 잠금도 문제가 된다. 따라서 이 사례는 이전 조회 뒤에 대기를 덧붙이지 않고, 깨끗한 연결에서 대기 → 새 조회 순서를 택한다. 명령의 Notes

저장 완료와 화면 반영을 따로 처리한다

주문 생성이 이미 커밋된 뒤 복제 대기가 끝나지 않았다고 하자. 이를 주문 실패로 보고 생성 요청부터 다시 보내면, 읽기 지연에 쓰기 중복이라는 새 문제를 붙일 수 있다. 조회 경로만 복구하는 편이 이 사례의 목적에 맞는다.

아래는 앱에서 선택할 수 있는 설계 분기표다. 특정 드라이버가 자동으로 제공하는 동작은 아니다.

관측 이 사례에서의 처리 사용자에게 줄 응답
재생 대기 성공 같은 서버에서 새 조회 확인된 주문 상세
시간 초과, 요청 기한 여유 있음 정상 주 서버인지 확인하고 조회만 우회 주 서버에서 읽은 상세
시간 초과, 기한 여유 부족 조회를 중단하고 후속 조회 경로 제공 저장은 완료됐고 상세 반영을 기다리는 상태
서버 역할 변경 또는 연결 중단 토폴로지와 쓰기 결과를 다시 확인 확인 전에는 주문 부재나 실패로 단정하지 않음

WAIT의 기본 standby_replay는 데이터 적용을 기다린다. standby_write·standby_flush는 각각 WAL 쓰기·영구 저장 지점이며 조회 가시성과 구별된다. 승격 시 받을 수 있는 not in recovery, 숫자 LSN만 비교하고 타임라인은 검증하지 않는다는 제약도 명령 문서에 있다. 모드와 승격 조건

이를 장애 전환에 적용하면, 숫자가 크다는 이유만으로 예전 주 서버의 주문이 새 서버에도 존재한다고 판단해서는 안 된다. 앱의 토폴로지 세대와 대기 위치를 함께 관리하고, 전환 뒤에는 업무 ID로 처리 상태를 재확인하는 설계가 필요하다. 복제되기 전에 유실된 커밋을 기다림 명령이 만들어 낼 수는 없다. 외부 캐시에서 반환하는 주문 상세도 DB 대기만으로 갱신되지 않는다.

주 서버 조회·동기 복제와 무엇이 다른가

저장 직후 필요한 조회가 드물다면, 해당 조회만 주 서버로 보내는 방안이 먼저 검토할 만하다. LSN 전달과 복제본 선택을 추가하지 않아 구조가 단순하다. 반대로 쓰기 뒤 이어지는 조회가 많고, 그 읽기를 복제본으로 분산해야 한다면 대기 방식의 도입 가치를 평가할 수 있다. 이 판단은 부하·운영 복잡도에 관한 가설이며 실제 처리량 우열을 측정한 결과는 아니다.

동기 복제에는 다른 선택지가 있다. synchronous_commit 문서에 따르면, 동기 standby를 구성한 상태의 remote_apply는 해당 standby의 적용 확인까지 커밋을 기다린다. on은 적용까지의 대기를 뜻하지 않는다. synchronous_standby_names가 비어 있으면 remote_apply만 설정해도 원격 적용을 기다리지 않는다.

선택 대기를 부담하는 지점 이 주문 사례에서 선택할 이유
저장 뒤 주 서버 조회 읽기를 주 서버에서 수행 필요한 조회가 적고 단순한 경로를 원함
비동기 복제 + WAIT 최신 쓰기가 필요한 읽기 일반 읽기는 그대로 분산하고 특정 조회만 위치 확인
동기 복제 + remote_apply 해당 설정의 쓰기 커밋 쓰기 완료의 의미에 동기 standby 적용까지 포함하려 함

마지막 방법도 임의의 모든 복제본이 따라잡았다는 뜻은 아니다. 조회 라우팅이 실제로 적용을 확인한 서버와 연결되는지 검토해야 한다. 어떤 방법이든 한 주문의 최신 상태를 읽는 문제와 여러 주문 사이의 업무 규칙을 지키는 문제는 구별된다. 동시 요청의 쓰기 편향과 제약 설계는 PostgreSQL 격리 수준 글에서 다룬다.

기다림 예산을 숫자로 정해 보는 사례

다음 수치는 가상의 요청 예산이다. 실제 PostgreSQL 성능이나 복제 지연 분포가 아니다. 상세 조회의 남은 시간이 250ms이고, 풀 획득·왕복에 30ms, 성공 뒤 조회에 40ms, 응답 처리에 20ms를 배정했다고 가정하자.

  • 복제 대기에 100ms를 쓰고 성공하면 30 + 100 + 40 + 20 = 190ms다.
  • 시간 초과 후 주 서버 조회에 총 80ms를 쓰면 30 + 100 + 80 + 20 = 230ms다. 여기서 80ms는 우회 연결 획득과 조회를 모두 포함한 가정이다.
  • 대기를 200ms로 늘린 뒤 같은 우회를 하면 330ms다. 250ms 기한을 넘으므로 이 조합을 허용할 수 없다.

이 계산의 용도는 빠른 DB를 고르는 것이 아니라 실패 분기의 시간을 먼저 남기는 것이다. 실제 앱에서는 각 단계 후의 남은 기한으로 판단하고, 꼬리 지연과 네트워크 오류를 따로 측정해야 한다. WAIT의 시간 제한 하나로 전체 요청이 그 시간 안에 끝난다고 볼 수 없다. 전체 기한을 하위 호출에 나누는 방법은 재시도·타임아웃 예산 글로 이어진다.

시험 환경에서 확인할 인수 조건

도입 시험은 격리된 19 시험 환경에서 시작한다. 아래 항목은 실행 결과가 아닌 확인할 인수 조건이다.

  1. 서버 버전과 실제 지원 옵션을 기록하고, 커밋 뒤 위치를 얻는지 확인한다.
  2. 복제본 적용이 늦은 경우에도 성공 뒤 조회가 주문을 찾는지 본다. 대기한 서버와 조회한 서버를 함께 기록한다.
  3. 시간 초과를 만든 뒤 조회만 우회하고, 주문 쓰기를 다시 호출하지 않는지 확인한다.
  4. 풀의 서버 재선택·이전 트랜잭션 잔류·장애 전환 상황을 따로 시험한다. 정상 경로 성공으로 이 조건까지 통과했다고 표시하지 않는다.

읽기 경로 점검 기록지를 저장해 팀의 풀·프록시·캐시 구조를 적어 볼 수 있다. 결과 칸은 모두 미확인으로 시작한다. 이 기능을 채택할 기준은 명령 하나의 성공보다, 저장 완료부터 사용자가 상세를 읽기까지의 경로를 설명하고 실패 때도 일관된 응답을 줄 수 있는지에 있다.

다시 읽을 때의 기준
  • 커밋 뒤 얻은 WAL 위치를 읽기 요청에 전달하고, 조회할 복제본에서 해당 위치의 재생이 끝났는지 확인한다.
  • WAIT 성공 뒤 다른 복제본으로 이동하거나 오래된 스냅샷을 읽으면 의도한 흐름이 깨질 수 있다. 연결과 조회 순서를 함께 설계한다.
  • 시간 초과는 쓰기 실패가 아니다. 조회 우회 또는 반영 대기 응답으로 처리하고 주문 생성을 다시 실행하지 않는다.
SOURCES & CONTEXT

근거와 원자료

자료의 발행 시점과 이번 글에서 참고한 범위를 함께 기록합니다.

  1. PostgreSQL 19 RC 1 Released! ↗PostgreSQL Global Development Group · 2026

    2026-10-15 발표; RC2/GA는 계획이며 실제 제공일 아님

  2. PostgreSQL 19 — WAIT ↗PostgreSQL Global Development Group · 2026 · 19 개발 문서

    19 개발 문서의 MODE/TIMEOUT/NO_THROW, snapshot/lock 제한과 timeline 한계. RC1 실행 검증 아님

  3. PostgreSQL 19 — Log-Shipping Standby Servers ↗PostgreSQL Global Development Group · 2026 · 19 개발 문서

    물리 비동기 복제와 Read-Your-Writes 절차; 커밋 뒤 LSN 전달

  4. PostgreSQL 19 — System Administration Functions ↗PostgreSQL Global Development Group · 2026 · 19 개발 문서

    WAL insert/write/flush 위치 정의

  5. PostgreSQL 19 — Write Ahead Log ↗PostgreSQL Global Development Group · 2026 · 19 개발 문서

    synchronous_commit remote_apply와 synchronous_standby_names 구성 조건

갱신 기록

19 개발 문서와 RC 일정을 확인하고 주문 조회의 연결·대기·복구 사례를 분석했다. DB 실행과 성능 측정은 미실시했다.

오류를 발견했다면 댓글로 알려 주세요 →
S
Starhunter

테크독해 운영·편집. AI·IT의 개념과 기술을 연결해 읽습니다.

QUESTIONS & DISCUSSION

이 글에 댓글 남기기

댓글 공개 방식을 확인하고 있습니다. 개인정보나 비밀 값은 적지 마세요.

댓글을 불러오는 중입니다.

    새 댓글

    8자 이상. 비밀번호를 잊으면 직접 수정·삭제할 수 없습니다. 다른 서비스와 다른 비밀번호를 사용해 주세요.

    내 댓글 확인·수정·삭제

    등록 후 안내된 댓글 번호와 비밀번호를 입력하세요. 승인 대기 중인 댓글도 확인할 수 있습니다.