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_level—postgresql.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로의 전이
- 운영자(또는 publication 생성 시 PostgreSQL)가 첫 logical replication slot 생성을 요청한다.
- 서버는 effective level이
logical미만이면 즉시 forced checkpoint를 돈다. - 이 checkpoint 직후의 LSN을 “guaranteed LSN"으로 기록하고, slot은 그 지점부터 시작한다.
- 그 LSN 이후의 WAL은
logical레벨 메타데이터를 함께 적기 시작한다.
내려갈 때 — replica로의 하강
- 마지막 logical slot이 사라진다.
- 서버는 다음 일반 checkpoint까지 그대로
logical을 유지한다. 즉시 내리지 않는다. - 그 checkpoint가 돌고 나면 effective level이 configured 값으로 복귀한다.
- 이후 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이 노드별로 따로 움직이는 곳에서는 모서리가 한 번씩 보인다.
참고 자료
- The wal_level You Set Is Not the wal_level You Get — The Build
- PostgreSQL 19 Release Notes
- WAL Configuration — PostgreSQL Documentation
- Logical Replication — PostgreSQL Documentation
- PostgreSQL 19 새 기능 총정리 — 이 블로그
- pgBackRest의 종료, 그리고 Barman·CNPG로 이어지는 길 — 이 블로그
- PostgreSQL 18의 autovacuum_worker_slots — 이 블로그
- Barman 시작하기 — 5개 명령어로 첫 백업과 PITR 복구까지 — 이 블로그