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 wal_level과 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를 즉시 실행합니다. 내려갈 때는 별도 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초 미만으로 설정되어 있지 않은지 살펴봅니다.

WAL 볼륨 모니터링에서는 PG19로 올린 직후 WAL 생성률이 줄어드는 변화를 정상으로 인식하는지 확인합니다. 감소를 장애로 판단해 알람을 보내지 않도록 해야 합니다. 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이 노드별로 따로 움직이는 곳에서는 모서리가 한 번씩 보여요.

참고 자료