PostgreSQL 19에서 wal_level이 의미를 바꿨다. 더 이상 “이 서버가 항상 쓰는 WAL 레벨"이 아니다. 하한값이다. 실제 effective level은 그 위에서 slot 상태에 따라 자동으로 움직인다. logical replication slot이 하나 생기면 effective level이 logical로 올라가고, 마지막 slot이 사라지면 다음 checkpoint에서 내려간다.

이 글은 The Build의 Christophe Pettus가 다룬 “The wal_level You Set Is Not the wal_level You Get”을 한국어로 풀고, cascading standby와 archived WAL에서 운영자가 한 번은 부딪힐 자리들을 함께 본다.

보험성 wal_level=logical 의 비용

기존 PostgreSQL에서 wal_level은 셋 중 하나로 못 박혀 있었다 — minimal, replica, logical. 그리고 변경은 재시작 파라미터다.

운영 패턴이 자연스럽게 굳어졌다. 지금 당장 logical replication을 쓰지 않더라도, 나중에 쓸 가능성이 조금이라도 있으면 일단 logical로 잡아둔다. 그래야 그날 새벽에 슬롯 하나 만들면서 재시작을 잡을 일이 없다. “혹시 모르니까” 라는 안전망이다.

이 안전망에는 비용이 붙는다.

  • wal_level = logical은 모든 변경에 대해 추가 메타데이터를 WAL에 적는다. row의 이전 이미지, replica identity 정보, 다중행 변경의 stream 표식 등이다.
  • 실제로 logical replication slot이 단 하나도 없어도 적힌다. “지금 안 쓰는데 적어두는 값"이 분 단위로 디스크에 흐른다.
  • 운영 환경에 따라 다르지만, replica 대비 WAL 볼륨이 10~30% 더 늘어나는 케이스가 흔히 보고된다.
  • WAL 볼륨이 늘면 archive 비용·streaming replication 대역폭·PITR 복구 시간이 같이 늘어난다.

“The right thing has corners.” — Christophe Pettus, The Build

PostgreSQL 19는 이 안전망의 비용을 덜기 위해 wal_level을 “고정 레벨"에서 “최소 보장"으로 바꿨다.

PG19의 전환 — configured vs effective wal_level

PostgreSQL 19에서 서버는 두 개의 WAL level을 가진다.

  • configured wal_levelpostgresql.conf 에 적힌 값. 여전히 재시작 파라미터다. 의미는 “이 서버가 어떤 경우에도 떨어지지 않을 하한값"이다.
  • effective wal_level — 서버가 실제로 지금 WAL에 쓰는 레벨. configured 값보다 위로 올라갈 수 있다.

규칙은 단순하다.

상태effective wal_level
logical replication slot이 하나 이상 살아있음logical
streaming replication 연결만 있음replica 또는 configured 중 높은 쪽
아무것도 없음configured 값 그대로

즉, configured를 replica로 잡아두고도 logical replication slot을 만드는 순간 서버가 알아서 logical로 올라간다. 마지막 slot이 사라지면 다음 checkpoint에서 다시 내려간다.

상태 전이도

flowchart TD
    A["configured wal_level = replica
effective = replica"] B["logical slot 생성 시도"] C["forced checkpoint"] D["effective = logical
guaranteed LSN 부터 적용"] E["slot 정상 운영"] F["마지막 slot 제거"] G["다음 checkpoint"] H["effective = replica
(configured 로 복귀)"] A --> B B --> C C --> D D --> E E --> F F --> G G --> H H --> A

핵심은 두 군데 — logical 진입 시점은 forced checkpoint 직후, 다시 내려가는 시점은 그다음 일반 checkpoint다. 비대칭이다.

동작 단계

올라갈 때와 내려갈 때를 따로 본다.

올라갈 때 — logical로의 전이

  1. 운영자(또는 publication 생성 시 PostgreSQL)가 첫 logical replication slot 생성을 요청한다.
  2. 서버는 effective level이 logical 미만이면 즉시 forced checkpoint를 돈다.
  3. 이 checkpoint 직후의 LSN을 “guaranteed LSN"으로 기록하고, slot은 그 지점부터 시작한다.
  4. 그 LSN 이후의 WAL은 logical 레벨 메타데이터를 함께 적기 시작한다.

내려갈 때 — replica로의 하강

  1. 마지막 logical slot이 사라진다.
  2. 서버는 다음 일반 checkpoint까지 그대로 logical을 유지한다. 즉시 내리지 않는다.
  3. 그 checkpoint가 돌고 나면 effective level이 configured 값으로 복귀한다.
  4. 이후 WAL은 다시 replica 메타데이터로 가벼워진다.

비대칭이 의도된 설계다. 올라갈 때는 slot이 막 만들어진 WAL을 못 읽으면 안 되니 즉시 돌리고, 내려갈 때는 굳이 별도 checkpoint를 잡아 운영 영향을 만들 이유가 없다.

운영 영향

가장 큰 효과는 WAL 볼륨 절감이다.

  • “혹시 모르니까 logical"로 잡아둔 클러스터는, configured를 replica로 내리고 PG19로 올리면 그대로 WAL이 줄어든다. 실제로 logical을 쓸 일이 생기면 그 순간 자동으로 올라가니 운영 부담은 늘지 않는다.
  • archive 비용·streaming replication 대역폭·PITR 복구 시간이 모두 따라 줄어든다.
  • 클라우드 관리형 PostgreSQL에서는 archive 저장 비용이 매월 청구되는 항목이라 체감이 크다.

부차 효과로는 logical replication을 사용 중인 동안에만 그 비용을 낸다는 것 — 일회성 마이그레이션을 위해 logical을 켰다가 끝나면 자동으로 비용이 사라진다.

함정과 주의사항

네 가지를 짚는다.

첫째, cascading standby의 비대칭. primary와 standby는 각자 effective level을 따로 가진다. primary의 마지막 logical slot이 사라져도 standby는 자신의 슬롯이 살아있는 한 effective logical을 유지한다. primary가 보내는 WAL은 그새 replica로 내려갈 수 있는데, standby는 자기 슬롯을 위해 그 WAL을 logical로 받아야 한다 — 이 경계가 운영자에게 보이지 않는 곳에서 정렬된다. PG19가 알아서 잘 처리하지만, replication lag 분석 시 헷갈리지 않으려면 “두 노드의 effective level은 따로 움직인다"는 사실을 머리에 둬야 한다.

둘째, archive에 mixed-level WAL이 섞인다. 같은 archive 디렉토리에 replica 시기의 WAL과 logical 시기의 WAL이 함께 쌓인다. PostgreSQL은 segment마다 metadata로 어떤 레벨인지 표시해두지만, “이 archive는 통째로 logical이다” 같은 가정을 두고 짠 PITR script가 있다면 다시 봐야 한다.

셋째, prepared transaction. 2-phase commit 자체의 동작은 변하지 않는다. 다만 prepared transaction이 많은 환경에서는 logical 전이 시점의 forced checkpoint가 의외로 오래 끌릴 수 있다 — 테스트 환경에서 한 번은 의도적으로 재현해 보고 timeout 설정을 검토하는 게 좋다.

넷째, 첫 slot 생성 시 checkpoint 대기. 새로 만든 publication이 logical slot을 생성하면 그 호출이 forced checkpoint를 기다린다. 평소 checkpoint 부담이 큰 시스템에서는 수십 초가 걸릴 수도 있다. 자동화 script가 CREATE SUBSCRIPTION 호출에 짧은 timeout을 걸어뒀다면 PG19 업그레이드 직후 한 번 흔들릴 가능성이 있다.

PITR / archive script 점검 포인트

PostgreSQL 19로 올라가기 전에 한 번씩 확인할 자리들이다.

  • archive level 가정 — script가 “이 archive는 logical 레벨"임을 전제로 메타데이터를 파싱하지는 않는가
  • slot 생성 timeout — 자동화 도구의 CREATE SUBSCRIPTION / pg_create_logical_replication_slot 호출에 30초 미만의 timeout이 걸려 있지 않은가
  • WAL 볼륨 모니터링 — PG19로 올린 직후 WAL 생성률이 줄어드는 변화를 정상으로 인식하는가 (감소를 장애로 알람 보내지 않도록)
  • standby 정렬 — cascading 구성에서 primary·intermediate·leaf의 effective level이 각각 어떻게 정렬되는지 그림으로 한 번 그려두기

“혹시 모르니까"라는 비용

logical replication을 본격적으로 쓰기 시작한 계기는 zero-downtime upgrade와 CDC 파이프라인이다. 한 번 켜놓으면 끝나는 게 아니라 “혹시 다음 마이그레이션 때 또 쓸지 모르니까” wal_level = logical 그대로 두는 패턴이 굳어졌다. 그 사이 WAL 볼륨이 10~30% 더 흐르고, archive 비용과 streaming replication 대역폭이 같이 늘어난다 — 평소엔 의식 못 하지만 PITR 복구 시간을 재 보면 한 번씩 보인다. PostgreSQL 19의 동적 wal_level은 이 “보험 비용"을 시점성 비용으로 바꾼다. 일회성 마이그레이션이 끝나면 slot만 정리해주면 자동으로 effective level이 내려가고 WAL이 가벼워진다. 운영자가 wal_level을 내릴지 말지 회의에 올릴 일이 없어진다는 것 — PG19가 일상에 주는 가장 큰 차이는 그쪽이다.

정리

PG18 이하PG19
wal_level 의 의미고정값하한값 (floor)
logical 전환 비용항상 부담slot 있는 동안만
WAL 볼륨 (logical 미사용 시)풀 부담replica 수준
첫 logical slot 생성즉시forced checkpoint 후
마지막 slot 제거 후 하강n/a다음 checkpoint

PostgreSQL 19는 운영자가 “혹시 모르니까"라는 이유로 영구히 짊어졌던 비용을 시점성 비용으로 바꿨다. logical replication이 필요한 그 순간에만 그 비용을 낸다. 기본 동작이 더 똑똑해진 변화지만, cascading standby와 archive처럼 effective level이 노드별로 따로 움직이는 곳에서는 모서리가 한 번씩 보인다.

참고 자료