본문으로 건너뛰기

"PostgreSQL" 태그로 연결된 49개 게시물개의 게시물이 있습니다.

모든 태그 보기

Barman PITR 워크북

· 약 7분

백업이 아니라 복구가 시험이다​

2편 마지막에 이렇게 적었어요. "백업의 가치는 백업이 아니라 복구에서 결정됩니다." 이 글은 그 한 줄을 직접 돌려보는 워크북이에요. Barman의 PITR target 옵션 4가지를 한 lab에서 한 번씩 시도해 봐요.

처음 보는 독자도 따라올 수 있게 환경을 짧게 정리합니다.

호스트역할Barman 라벨
demo-pg01PostgreSQL 17 primary해당 없음
demo-barman01Barman 3.18 서버pg01

pg01은 Barman 서버 라벨이고, demo-pg01은 실제 호스트네임이므로 두 이름은 별개입니다. 환경 셋업이 처음이라면 2편을 먼저 보는 편이 빠릅니다.

참고로 barman recover와 barman restore는 같은 명령입니다. Barman 3.x에서는 restore가 새 권장 이름이지만 recover도 그대로 동작합니다. 이 글은 2편과 톤을 맞춰 recover로 통일했습니다.

시나리오 준비​

지웠다가 살릴 데이터를 만듭니다.

PITR을 그럴듯하게 돌려 보려면 의도적으로 손상된 시점이 필요합니다. 다음 SQL을 demo-pg01에서 미리 실행해 둡니다.

# T0: 기준 데이터
sudo -u postgres psql <<'SQL'
CREATE TABLE notes (
id int PRIMARY KEY,
body text,
ts timestamptz default now()
);
INSERT INTO notes(id, body)
SELECT g, 'note-' || g FROM generate_series(1, 1000) g;
SQL
# T1 (예: 14:30:00): 안전 시점 — 이후로 되감을 라벨 생성 + XID 기록
sudo -u postgres psql <<'SQL'
BEGIN;
SELECT pg_create_restore_point('safe-state'); -- 시나리오 C 용
SELECT pg_current_xact_id(); -- 시나리오 B 용 — 예: 12345
COMMIT;
SQL
# T2 (예: 14:32:00 이후): 사고 — 테이블 삭제
sudo -u postgres psql -c "DROP TABLE notes;"

이제 notes 테이블이 사라졌습니다. base backup이 T0 이전에 떠 있고 WAL이 계속 수집되고 있다고 가정합니다(2편의 lab 그대로). 이 base backup과 WAL을 가지고 T1 직후 / XID 12345 직후 / 명시 라벨 / base backup 직후 4가지 시점으로 되감아 봅니다.

base backup부터 테이블 삭제까지의 PITR 복구 좌표

복원 대상은 빈 디렉토리입니다. 매 시나리오 사이에 비우고 시작합니다.

sudo -u barman rm -rf /var/lib/barman/restore && \
sudo -u barman mkdir -p /var/lib/barman/restore

시나리오 A: --target-time​

시계로 되감는 방식이 가장 직관적입니다. 사고 직전 시각으로 돌립니다.

sudo -u barman barman recover pg01 latest /var/lib/barman/restore \
--target-time "2026-05-06 14:32:00"

기대 동작은 14:32:00 시점까지 WAL을 replay한 뒤 멈추는 것입니다. notes 테이블은 살아 있고, DROP TABLE에는 도달하지 않습니다.

타임존은 Barman 서버의 시스템 시간대가 기본입니다. 다른 TZ로 명시하려면 2026-05-06 14:32:00+09 형태로 붙입니다. 운영에서는 항상 명시하는 편이 안전합니다.

"Recovery targets must be a value after the end of the backup." — base backup 종료(End time) 이전 시점으로는 복구할 수 없습니다. 시점이 End time보다 이르면 즉시 실패합니다.

시나리오 B: --target-xid​

트랜잭션 ID로 되감습니다.

시계는 누적된 운영 환경에서 의외로 부정확합니다. Barman 호스트와 PostgreSQL 호스트의 시계가 살짝 어긋나 있거나, 동시에 여러 트랜잭션이 들어올 수 있기 때문입니다. 트랜잭션 ID(XID)가 가장 정확한 좌표입니다.

sudo -u barman barman recover pg01 latest /var/lib/barman/restore \
--target-xid 12345

XID 12345가 commit된 직후까지 replay하고 멈추는 동작을 기대합니다. T1 시점에 기록해 둔 XID가 사고 직전 마지막 안전 트랜잭션이므로 여기에서 멈추면 notes는 살아 있습니다.

항목내용
강점시점이 논리적으로 정확하며 시계 오차/동시 트랜잭션의 영향이 없음
약점사고 직후가 되어서야 그 XID를 알게 되므로 사고 직전에 미리 기록해 두는 편이 이상적
보조pg_waldump로 WAL을 검사해 commit 레코드의 XID 시퀀스 추적 가능

--exclusive 플래그를 같이 주면 그 XID 직전까지만 replay합니다(그 트랜잭션 자체는 제외). 기본값은 그 XID 포함입니다.

시나리오 C: --target-name​

명시한 라벨로 되감습니다.

운영 중 위험한 작업 직전에 라벨을 만들어 두는 패턴입니다.

-- 사고 직전(T1)에 미리 만들어 둔 라벨
SELECT pg_create_restore_point('safe-state');
sudo -u barman barman recover pg01 latest /var/lib/barman/restore \
--target-name 'safe-state'

safe-state restore point까지 replay하고 멈추는 동작을 기대합니다.

이 옵션의 가치는 언어가 자연스럽다는 점입니다. 시각/XID는 사고 후 재구성해야 하지만 라벨은 팀이 기억할 만한 이름입니다. before-migration-2026q2, pre-DROP-INDEX-experiment 같은 이름을 쓸 수 있습니다. 운영 가이드를 "위험한 ALTER 직전엔 restore point부터 만든다"로 정착시키면 PITR이 한층 평이해집니다.

시나리오 D: --target-immediate​

base backup 직후로 되감습니다.

가장 단순한 옵션입니다. 마지막 base backup이 일관성을 확보하는 그 시점까지만 replay하고 멈춥니다. 즉 base backup 종료 직후의 클러스터 상태로 일어섭니다.

sudo -u barman barman recover pg01 latest /var/lib/barman/restore \
--target-immediate

"가장 최근 base backup의 그 순간으로 일단 돌려놓고, 이후 사고 영향을 분리해 분석하고 싶다" 같은 forensic 시나리오에서 사용합니다. WAL replay를 최소화해 빠르게 일관성 상태에 도달합니다.

그 외 옵션 빠른 참조​

옵션의미비고
--target-lsnLSN(3/64000000 형식)으로 되감기XID보다 더 세밀, 특정 WAL 위치를 정확히 알 때
--target-tli특정 timeline으로 복구latest / current / 숫자 ID
--target-action도달 후 동작: shutdown / pause / promote미지정 시 PostgreSQL이 paused 후 운영자 결정 대기
--exclusivetarget 직전까지(target 자체 제외) replay기본은 target 포함
--standby-modereplica로 일어나도록 standby.signal 생성target과 무관한 옵션

--target-action이 특히 중요합니다. 지정하지 않으면 paused 상태가 되어, 운영자가 SELECT pg_wal_replay_resume()을 직접 호출할 때까지 새 트랜잭션이 돌지 않습니다. 이걸 모르면 "왜 안 살아나지" 하고 한참 헤매게 됩니다.

backup_id=auto(가장 최근 backup 자동 선택)일 때는 제약이 있습니다. --target-time, --target-lsn, --target-tli만 허용됩니다. --target-xid나 --target-name을 쓰려면 명시적인 backup-id를 지정해야 합니다(barman list-backup pg01로 확인).

시점을 정하는 법​

barman show-backup과 pg_waldump로 좌표를 찾습니다.

PITR의 절반은 어디로 되감을지 정하는 일입니다. 두 명령어가 그 좌표를 줍니다.

sudo -u barman barman show-backup pg01 latest

출력에서 다음 항목을 봅니다.

항목의미
Begin time / End timebase backup 경계이며 그 이전 시점에는 도달 불가
Begin LSN / End LSNbase backup의 LSN 경계이며 --target-lsn 사용 시 기준점
Begin Offset / End OffsetWAL 파일 내 위치

WAL을 더 세밀하게 보려면 pg_waldump로 commit 레코드를 확인합니다. streaming/에는 수신 중인 .partial 파일만 머물고, 완성된 WAL은 wals/<앞 16자리>/ 아래로 아카이브됩니다. 아래 명령은 Barman 호스트에 PostgreSQL 17 클라이언트 패키지(pg_waldump 포함)가 설치돼 있다는 전제입니다. Barman에 WAL 압축을 설정했다면 파일을 먼저 풀어야 합니다.

sudo -u barman /usr/pgsql-17/bin/pg_waldump \
/var/lib/barman/pg01/wals/0000000100000000/000000010000000000000004 \
| grep COMMIT | head

각 줄에 LSN, XID, timestamp가 함께 나오므로, 사고 직전의 어떤 좌표를 사용할지 골라잡기 쉬워집니다.

자주 만나는 함정 5가지​

증상원인해결
Recovery targets must be a value after the end of the backuptarget이 base backup 종료 이전barman show-backup의 End time 이후로 지정하거나 더 오래된 backup 사용
복구 후 PostgreSQL이 안 살아남--target-action 미지정으로 paused 상태SELECT pg_wal_replay_resume(); 또는 처음부터 --target-action promote
timeline mismatch이전 복구 후 새 timeline으로 진입--target-tli latest 또는 명시적 timeline ID
target-name 못 찾음restore point가 현재 timeline의 WAL에 없음pg_create_restore_point가 해당 timeline에 기록됐는지 확인
barman recover가 아무 진행 안 함--remote-ssh-command로 cross-host 복원인데 SSH 키 미설치barman 사용자에서 postgres@<host>로 키 기반 접속 미리 잡기

정리​

항목내용
4가지 target--target-time (시계) / --target-xid (XID) / --target-name (라벨) / --target-immediate (base 직후)
가장 정확XID이며 시계 오차/동시 트랜잭션과 무관
가장 운영 친화--target-name이며 위험 작업 직전 pg_create_restore_point를 만드는 패턴
시점 결정barman show-backup + pg_waldump 조합
필수 동반--target-action(기본 paused), --exclusive(stop-before vs include)
backup_id=auto 제약--target-time, --target-lsn, --target-tli만 허용하며 그 외는 명시 backup-id 필요

PITR은 두 단계로 나뉘어요. 시점을 정하는 일이 반이고 복구하는 일이 반인데, 보통은 시점을 정하는 쪽이 더 어려워요.

참고 자료​

Barman rsync 모드

· 약 7분

streaming은 알겠고, rsync는 언제 쓰나​

2편에서 Barman의 streaming-only 모델을 셋업했어요. 이 글에서는 같은 lab을 rsync 모델로 바꾸거나 처음부터 rsync로 셋업하고, 왜 rsync를 골랐는지 정리해 보려고 해요.

처음 보는 독자도 따라올 수 있게 환경을 짧게 정리합니다.

호스트역할Barman 라벨
demo-pg01PostgreSQL 17 primary해당 없음
demo-barman01Barman 3.18 서버pg01

pg01은 Barman 서버 라벨이고, demo-pg01은 실제 호스트네임입니다. 환경 셋업이 처음이라면 2편을 먼저 보고 오는 편이 빠릅니다.

rsync 모델 vs streaming 모델 한눈에​

항목rsync 모델 (이 글)streaming 모델 (2편)
도입 시기Barman 1.x (2012-)Barman 2.0 (2016-)
전송 채널SSH + rsyncPostgreSQL streaming replication
WAL 수집archive_command (WAL 세그먼트 완료 시 push)pg_receivewal (실시간 stream)
의존양방향 SSH 키replication slot, replication user
증분 dedup하드링크 (reuse_backup = link)(PG 17+ 블록 레벨 점진 이행)
병렬 복사parallel_jobs = N(단일 stream)
적합 환경베어메탈/VM, SSH 통제 가능컨테이너/K8s, SSH 미허용

이 글의 주제는 다섯/여섯 번째 행에 있는 하드링크 dedup과 parallel_jobs입니다. 둘 다 rsync 모델 고유의 강점입니다.

양방향 SSH 셋업​

rsync 모델은 두 방향의 SSH가 필요합니다.

방향용도
barman@demo-barman01 → postgres@demo-pg01base backup 시 rsync로 데이터 디렉토리 풀링
postgres@demo-pg01 → barman@demo-barman01archive_command로 WAL을 Barman에 푸시

비밀번호 없이 통과해야 cron이 자동으로 돌므로 SSH 키 기반입니다.

Barman rsync 모드의 양방향 SSH 흐름

첫 번째 방향은 demo-barman01의 barman 사용자에서 설정합니다.

sudo -u barman ssh-keygen -t ed25519 -N "" -f ~barman/.ssh/id_ed25519 \
-C "barman@demo-barman01"

# 공개키를 demo-pg01의 ~postgres/.ssh/authorized_keys에 등록
sudo -u barman ssh-copy-id postgres@demo-pg01

# 검증 — 비밀번호 없이 통과해야 함
sudo -u barman ssh postgres@demo-pg01 'echo ok'

두 번째 방향은 demo-pg01의 postgres 사용자에서 설정합니다.

sudo -u postgres ssh-keygen -t ed25519 -N "" -f ~postgres/.ssh/id_ed25519 \
-C "postgres@demo-pg01"
sudo -u postgres ssh-copy-id barman@demo-barman01
sudo -u postgres ssh barman@demo-barman01 'echo ok'

운영 환경에서는 authorized_keys에 from="..." 호스트 제약이나 command="..." 락다운을 거는 편이 안전합니다. 이 글은 lab이라 단순화했습니다.

PostgreSQL 측의 archive_command 활성화​

/var/lib/pgsql/17/data/postgresql.conf 핵심 항목:

listen_addresses = '*'
wal_level = replica
archive_mode = on
archive_command = 'barman-wal-archive -U barman demo-barman01 pg01 %p'

barman-wal-archive는 barman-cli 패키지의 명령이라 demo-pg01에 따로 설치해야 archive_command가 동작합니다.

# demo-pg01에서 실행
sudo dnf install -y barman-cli

내부적으로 SSH로 barman@demo-barman01에 접속해 WAL 파일을 /var/lib/barman/pg01/incoming/에 정확히 전달합니다 — cp나 직접 scp하는 것보다 안전합니다.

pg_hba.conf에는 Barman의 conninfo 접속을 열어 둡니다. replication slot은 rsync 모델에서 필수가 아니지만, barman check가 PostgreSQL 메타데이터를 읽으려면 이 접속이 필요합니다.

host postgres barman demo-barman01 scram-sha-256

PostgreSQL 재시작 후 사용자 생성:

sudo systemctl restart postgresql-17
sudo -u postgres psql <<'SQL'
CREATE USER barman WITH ENCRYPTED PASSWORD 'changeme';
GRANT pg_read_all_settings, pg_read_all_stats TO barman;
GRANT EXECUTE ON FUNCTION pg_backup_start(text, boolean) TO barman;
GRANT EXECUTE ON FUNCTION pg_backup_stop(boolean) TO barman;
GRANT EXECUTE ON FUNCTION pg_switch_wal() TO barman;
GRANT EXECUTE ON FUNCTION pg_create_restore_point(text) TO barman;
SQL

streaming 모델과 달리 REPLICATION 속성/replication slot은 생성하지 않습니다.

Barman 측의 backup_method = rsync 설정​

/etc/barman.d/pg01.conf를 다음과 같이 설정합니다.

[pg01]
description = "Production PostgreSQL primary (rsync mode)"
ssh_command = ssh postgres@demo-pg01
conninfo = host=demo-pg01 user=barman dbname=postgres
backup_method = rsync
parallel_jobs = 2
reuse_backup = link
archiver = on
retention_policy = RECOVERY WINDOW OF 4 WEEKS

핵심 다섯 줄의 의미는 이렇습니다.

키의미
backup_method = rsyncbase backup 때 SSH+rsync로 데이터 디렉토리를 가져옵니다
ssh_command = ssh postgres@demo-pg01rsync가 사용할 SSH 명령입니다. barman 사용자에서 postgres@demo-pg01로 접속합니다
parallel_jobs = 2병렬 rsync worker 수입니다. 디스크/네트워크 여유에 맞춰 조정합니다
reuse_backup = link이전 backup에서 변경되지 않은 파일을 하드링크로 재사용합니다. 핵심 dedup 옵션입니다
archiver = onarchive_command로 들어오는 WAL을 자동으로 처리합니다

streaming 모델의 streaming_conninfo, streaming_archiver, slot_name 키는 모두 사용하지 않습니다.

설정 검증:

sudo -u barman barman check pg01

receive-wal running은 streaming 모델 전용이므로 rsync 모드에서는 항목이 빠지거나 disabled로 나옵니다. 그 외 항목이 모두 OK면 셋업이 끝난 것입니다.

첫 backup + 하드링크 dedup 측정​

# 첫 base backup
sudo -u barman barman backup pg01

기대 출력 (요약):

Starting backup using rsync-over-ssh method for server pg01 ...
Copy done (time: 12 seconds)
Backup size: 41.5 MiB

이번에는 의도적으로 작은 변경만 만든 뒤 두 번째 backup을 수행합니다.

# 데이터 일부 변경 (작은 테이블 하나 추가)
sudo -u postgres psql -c "CREATE TABLE dedup_test AS SELECT g AS id FROM generate_series(1, 1000) g;"

# 두 번째 base backup — reuse_backup = link 가 동작
sudo -u barman barman backup pg01
sudo -u barman barman list-backup pg01

이제 디스크 사용량을 두 가지 방식으로 잽니다.

# 실제 디스크 사용량 (하드링크는 한 번만 카운트)
sudo du -sh /var/lib/barman/pg01/base/
# 예: 42.0 MiB

# 논리 사용량 (-l: 하드링크를 중복 카운트해 backup별로 따로 합산)
sudo du -shl /var/lib/barman/pg01/base/
# 예: 83 MiB

두 측정값의 차이인 41 MiB ≈ 첫 backup 크기가 dedup으로 절약된 디스크 공간입니다. 두 번째 backup은 변경된 파일만 새로 복사되고 나머지는 첫 backup 파일에 hardlink로 연결되므로, backup 횟수가 늘수록 누적 절약 효과가 커집니다.

두 백업이 변경 없는 파일의 디스크 데이터를 공유하는 하드링크 구조

rsync를 선택하는 기준 4가지​

기준rsync 유리streaming 유리
환경베어메탈/VM, SSH 통제 가능컨테이너/K8s, SSH 미허용
디스크 절약하드링크 dedup → 동일 backup 다수 보관 시 절약 큼블록 레벨 dedup은 PG 17+로 점진 이행 중
병렬화parallel_jobs로 N개 worker단일 stream
운영 부담양방향 SSH 키 관리replication slot 관리

베어메탈/VM에서 디스크 효율과 병렬 복사가 중요하면 rsync가 잘 맞습니다. 컨테이너/K8s에서 SSH를 피하고 운영을 단순화하려면 streaming이 유리합니다. 둘 다 가능한 환경이라면 운영자의 SSH 키 관리 부담이 적은 streaming이 무난합니다.

streaming과 rsync 사이의 전환​

같은 라벨로 두 모델을 동시에 운영할 수는 없습니다. backup_method는 하나만 지정할 수 있기 때문입니다. 전환 절차는 다음과 같습니다.

# 1단계: 기존 라벨 비활성화
[pg01]
active = false
...
# 2단계: 새 conf 파일로 다른 라벨 만들기 (예: pg01-rsync)
[pg01-rsync]
backup_method = rsync
...

새 라벨에 backup이 안정적으로 들어오는 것이 확인되면, 보통 1주일+ 경과/복구 리허설 1회 이상을 기준으로 기존 라벨을 폐기합니다.

운영 환경에서는 기존 backup을 즉시 버리지 않습니다. 보존 정책에 따라 자연 만료될 때까지 두 라벨을 함께 보관하면 이전 시점 PITR이 필요할 때 안전망이 됩니다.

정리​

항목내용
백업 모델backup_method = rsync, SSH+rsync 기반
WAL 수집archive_command = 'barman-wal-archive ...'
SSH 방향양방향 (rsync용 + archive_command용)
핵심 dedupreuse_backup = link, 하드링크 기반이며 백업 횟수가 누적될수록 절약 큼
병렬 복사parallel_jobs = N
streaming과의 관계동시 사용 불가 (라벨당 한 모델), 전환 시 새 라벨로 병행 운영 후 폐기

streaming은 운영 단순화에 강하고 rsync는 디스크 효율과 병렬화에 강해요. 환경 때문에 한쪽을 골라야 하는 상황이 아니라면 streaming부터 시도해 보는 편이 무난해요.

참고 자료​

Barman 빠른 시작

· 약 8분

어떻게 Barman인가​

1편이 "왜 Barman인가"였다면, 이 글은 "어떻게 Barman인가"예요. 1편에서 Barman의 14년 궤적과 아키텍처를 정리했어요. 이번에는 같은 자리에서 한 발짝 더 들어가 두 대의 Rocky Linux 머신을 준비하고, streaming-only 모드로 PostgreSQL을 설정한 뒤, 5개 명령어로 첫 백업과 PITR 복구까지 끝내 봅니다.

이번에 다룰 시나리오를 한 화면에 펼치면 다음과 같습니다.

단계명령어의미
1barman check pg01PG 연결, streaming, 권한 확인
2barman backup pg01첫 base backup
3barman list-backup pg01카탈로그 조회
4barman recover pg01 latest <dir>최신 백업 복원
5barman recover ... --target-time "..."임의 시점 (PITR) 복원

읽으면서 따라 할 수 있도록 모든 명령어와 설정 파일 내용을 그대로 옮겨 둡니다.

사전 준비​

두 호스트로 lab 환경을 구성합니다.

호스트역할OS
demo-pg01PostgreSQL 17 primaryRocky Linux 9
demo-barman01Barman 3.18 서버Rocky Linux 9

두 호스트가 hostname으로 서로 통신한다고 가정합니다(DNS 또는 /etc/hosts). 단일 머신에서 시험하려면 demo-pg01과 demo-barman01을 모두 localhost로 두고 진행해도 됩니다.

PostgreSQL에서 Barman으로 base backup과 WAL이 전달되는 흐름

설치​

PGDG 저장소에서 설치합니다.

demo-pg01과 demo-barman01 양쪽에 PGDG 저장소를 등록합니다.

sudo dnf install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-9-x86_64/pgdg-redhat-repo-latest.noarch.rpm
sudo dnf -qy module disable postgresql

demo-pg01에서 PostgreSQL 17 설치/초기화:

sudo dnf install -y postgresql17-server postgresql17-contrib
sudo /usr/pgsql-17/bin/postgresql-17-setup initdb
sudo systemctl enable --now postgresql-17

demo-barman01에 Barman 3.18 설치:

sudo dnf install -y barman barman-cli

설치 시 barman 시스템 사용자가 자동 생성되며, 백업 카탈로그 기본 경로는 /var/lib/barman입니다. /etc/cron.d/barman도 함께 깔리므로 별도 systemd timer 설정 없이 cron이 매 분 한 번 barman cron을 돌립니다.

설정 1: PostgreSQL 측 (demo-pg01)​

streaming 백업은 PostgreSQL의 replication 프로토콜 위에서 동작하므로, replication user와 replication slot이 필요합니다.

/var/lib/pgsql/17/data/postgresql.conf 핵심 항목:

listen_addresses = '*'
wal_level = replica
max_wal_senders = 10
max_replication_slots = 10

/var/lib/pgsql/17/data/pg_hba.conf에 Barman 측 접속을 열어 둡니다.

# TYPE DATABASE USER ADDRESS METHOD
host replication barman demo-barman01 scram-sha-256
host postgres barman demo-barman01 scram-sha-256

PostgreSQL을 재시작한 후 사용자와 슬롯을 생성합니다.

sudo systemctl restart postgresql-17
sudo -u postgres psql <<'SQL'
CREATE USER barman WITH REPLICATION ENCRYPTED PASSWORD 'changeme';
GRANT EXECUTE ON FUNCTION pg_backup_start(text, boolean) TO barman;
GRANT EXECUTE ON FUNCTION pg_backup_stop(boolean) TO barman;
GRANT EXECUTE ON FUNCTION pg_switch_wal() TO barman;
GRANT EXECUTE ON FUNCTION pg_create_restore_point(text) TO barman;
GRANT pg_read_all_settings, pg_read_all_stats TO barman;
SELECT pg_create_physical_replication_slot('barman');
SQL

실제 운영에서는 changeme을 비밀 관리자(Vault, AWS Secrets Manager 등)로 옮기고 .pgpass 또는 환경변수로 분리해야 합니다.

설정 2: Barman 측 (demo-barman01)​

/etc/barman.conf는 default 값 그대로 두는 편이 무난합니다. 서버별 설정만 추가합니다(default 항목 자체는 별도 글에서 다룹니다).

/etc/barman.d/pg01.conf:

[pg01]
description = "Production PG primary"
conninfo = host=demo-pg01 user=barman dbname=postgres
streaming_conninfo = host=demo-pg01 user=barman
backup_method = postgres
streaming_archiver = on
slot_name = barman
create_slot = manual
retention_policy = RECOVERY WINDOW OF 4 WEEKS

[pg01]은 Barman 서버 라벨이자 서버 식별자입니다. CLI(barman backup pg01), 카탈로그 디렉토리(/var/lib/barman/pg01/...), conf 파일명(pg01.conf)에 같은 값을 씁니다. 실제 호스트네임 demo-pg01과는 별개이며, Barman이 부르는 이름과 네트워크가 부르는 이름을 분리한 것입니다.

conninfo = host=demo-pg01 ...에는 Barman이 PostgreSQL에 접속할 때 사용하는 실제 호스트네임을 지정하므로 라벨과 달라도 자연스럽습니다. backup_method = postgres는 pg_basebackup을 통한 streaming 백업을 뜻하고, streaming_archiver = on은 WAL을 pg_receivewal 방식으로 받도록 설정합니다. slot_name = barman은 앞에서 만든 replication slot을 사용합니다. create_slot = manual로 지정한 이유는 슬롯을 SQL로 이미 만들었으므로 Barman이 자동 생성하지 않게 하기 위해서입니다.

비밀번호는 barman 사용자의 ~/.pgpass로 분리합니다.

sudo -u barman tee ~barman/.pgpass > /dev/null <<'EOF'
demo-pg01:5432:*:barman:changeme
EOF
sudo chmod 600 ~barman/.pgpass
sudo chown barman:barman ~barman/.pgpass

5개 명령어 시나리오​

이제부터는 모든 명령어를 barman 사용자로 실행합니다. sudo -i -u barman으로 barman 셸에 들어가거나, 명령어마다 sudo -u barman을 앞에 붙입니다.

1. barman check pg01​

셋업을 검증합니다.

sudo -u barman barman check pg01

기대 출력 (요약):

Server pg01:
PostgreSQL: OK
wal_level: OK
replication slot: OK
directories: OK
retention policy settings: OK
pg_basebackup: OK
pg_basebackup compatible: OK
systemid coherence: OK
pg_receivexlog: OK
receive-wal running: OK
archiver errors: OK

한 줄이라도 FAILED가 나오면 그 항목에 셋업 단계의 문제가 있습니다. 가장 자주 보이는 receive-wal running: FAILED는 cron이 아직 한 번도 돌지 않았거나 slot 이름이 어긋났다는 뜻입니다. 한 번 강제로 돌려 두는 편이 빠릅니다.

sudo -u barman barman cron

2. barman backup pg01​

첫 base backup을 실행합니다.

sudo -u barman barman backup pg01

기대 출력 (요약):

Starting backup using postgres method for server pg01 in /var/lib/barman/pg01/base/20260504T143012
Backup start at LSN: 0/3000028
Starting backup copy via pg_basebackup for 20260504T143012
Copy done (time: 12 seconds)
Backup size: 41.5 MiB
Backup end at LSN: 0/4000060
Marking backup as DONE
Backup completed (start time: 2026-05-04 14:30:12, elapsed time: 14 seconds)

/var/lib/barman/pg01/base/<timestamp>/ 아래에 base backup이 쌓입니다. WAL은 /var/lib/barman/pg01/streaming/으로 수신된 뒤 /var/lib/barman/pg01/wals/ 아래에 아카이브됩니다.

3. barman list-backup pg01​

카탈로그를 조회합니다.

sudo -u barman barman list-backup pg01

기대 출력:

pg01 20260504T143012 - F - 2026-05-04 14:30:26 - Size: 41.5 MiB - WAL Size: 16 MiB

F는 full backup입니다. backup-id는 timestamp 기반(20260504T143012)이며, latest 키워드를 별칭으로 쓸 수 있습니다.

상세 보기:

sudo -u barman barman show-backup pg01 latest

Begin time / End time / Begin LSN / End LSN 등 PITR 타깃을 결정할 때 필요한 값이 모두 여기에 있습니다.

4. barman recover​

최신 백업으로 복원합니다.

복원 대상은 빈 디렉토리여야 합니다. PostgreSQL이 새로 기동할 자리를 미리 비워 둡니다.

sudo mkdir -p /var/lib/barman/restore
sudo chown barman:barman /var/lib/barman/restore

sudo -u barman barman recover pg01 latest /var/lib/barman/restore

복원이 끝나면 그 디렉토리 안에 base backup이 풀리고, recovery.signal과 postgresql.auto.conf의 restore_command 항목이 자동 생성됩니다. PostgreSQL을 그 데이터 디렉토리로 띄우면 곧장 기동합니다.

다른 호스트로 직접 복원하려면 --remote-ssh-command "ssh postgres@<host>"를 추가합니다. 이때 barman 사용자에서 그 호스트의 postgres로 SSH 키 기반 접속이 미리 설정되어 있어야 합니다.

5. barman recover --target-time​

특정 시점으로 되감는 PITR을 실행합니다.

sudo -u barman barman recover pg01 latest /var/lib/barman/restore \
--target-time "2026-05-04 15:00:00"

복원 시점은 base backup 종료(End time) 이후, 가장 마지막에 받은 WAL 이전 사이여야 합니다. 정확한 경계가 헷갈리면 barman show-backup pg01 latest의 Begin time / End time을 기준으로 잡습니다.

다른 PITR 타깃 옵션도 같은 자리에 지정합니다.

옵션의미
--target-time시점 기준
--target-xid트랜잭션 ID 기준
--target-namepg_create_restore_point()로 만든 명시적 라벨
--target-immediatebase backup 직후 일관성 시점

일상 운영​

cron 한 줄과 보존 정책을 설정합니다.

설치 시 동봉된 /etc/cron.d/barman이 매 분 barman cron을 실행합니다(WAL 수신과 아카이브 정리 담당). 정기 백업은 별도 cron 한 줄로 잡는 방식이 표준입니다.

# /etc/cron.d/barman-backup — 매일 02:00에 모든 등록 서버 백업
0 2 * * * barman /usr/bin/barman backup all

보존 정책은 서버별 conf 한 줄로 끝납니다.

retention_policy = RECOVERY WINDOW OF 4 WEEKS
# 또는 개수 기반:
# retention_policy = REDUNDANCY 5

RECOVERY WINDOW는 "이 시점부터 N 단위(WEEKS/DAYS) 전까지 PITR이 가능하도록 보장"한다는 의미입니다. 지난 4주 동안 임의 시점으로 되감을 수 있도록 base backup과 WAL을 함께 보존합니다. 정책에서 벗어난 backup은 barman cron이 자동으로 정리합니다.

자주 만나는 에러 빠른 가이드​

증상원인한 줄 해결
receive-wal running: FAILEDstreaming WAL receiver 미실행barman cron 수동 실행, slot 이름/권한 재점검
replication slot: FAILEDPG 측 슬롯이 없음 / 이름 불일치pg_create_physical_replication_slot('barman') 다시 실행
pg_basebackup: FAILEDreplication 권한/pg_hba 미흡barman 사용자의 REPLICATION 속성 확인, pg_hba에 host replication 행
Connection refusedlisten_addresses, 방화벽postgresql.conf의 listen_addresses = '*', firewalld에서 5432 허용

barman check는 한 번에 끝내려 들기보다 "FAIL 한 줄씩 잡아 나가는 도구"로 보면 마음이 편합니다. 위 4개를 해결하면 첫 셋업의 90%가 끝납니다.

정리​

항목내용
백업 모델streaming-only (SSH 없이 pg_basebackup + replication slot)
카탈로그 위치/var/lib/barman/pg01/{base,streaming,wals}
일상 명령어barman cron (자동) + barman backup all (cron 1회/일)
검증 명령어barman check pg01 — 셋업 직후/이상 발생 시 첫 진단
PITRbarman recover ... --target-time "..." 한 줄
보존 정책RECOVERY WINDOW OF 4 WEEKS 권장

백업의 가치는 백업이 아니라 복구에서 결정돼요. 셋업 직후 PITR을 한 번은 반드시 돌려봐요.

참고 자료​

Barman 14년의 발자취

· 약 9분

pgBackRest가 멈춘 자리, Barman을 다시 본다​

지난 글에서 pgBackRest의 종료를 정리하면서, 마이그레이션 평가의 가장 유력한 후보로 Barman(바만)을 짚었어요. 표준이 멈췄다는 4월 말의 며칠이 시끄러웠던 동안, Barman은 평소처럼 일하고 있었어요. 3월에 v3.18.0을 조용히 릴리스했고, 깃허브 이슈는 평상시처럼 열리고 닫혔는데 14년째 그러고 있어요.

오픈소스 도구의 가치는 시끄럽지 않게 잘 굴러갔다는 사실에서 가장 잘 드러납니다. 그 14년이 어떻게 흘러왔는지, Barman이라는 도구가 어떤 모양으로 생겼는지를 이 글에서 1편으로 정리하고, 실전 사용법은 2편에서 다룹니다.

토스카나 프라토에서 시작하다 (2001 → 2008 → 2012)​

Barman의 출발점을 이해하려면 먼저 모회사 2ndQuadrant Italia의 시작점으로 가야 합니다.

시점사건
2001영국에서 2ndQuadrant Ltd. 창립 (Simon Riggs(시몬 리그스))
2008-05-212ndQuadrant Italia 설립 (이탈리아 프라토), Gabriele Bartolini(가브리엘레 바르톨리니), Marco Nenciarini(마르코 넨치아리니) 등
2012Barman 첫 공개 (저작권 © 2012-)

토스카나 한가운데의 프라토에서 시작된 이 작은 컨설팅사가 PostgreSQL 생태계에 남긴 자취가 적지 않습니다. Logical Replication 초창기 구현에서 시작해 Barman, repmgr(고가용성 매니저), pglogical까지, 그 진영에서 나온 도구가 운영 도구 라인업의 한 축을 채웠습니다. PostgreSQL 핵심 개발자인 Simon Riggs가 영국에서 비전을 세우고, 이탈리아 팀이 운영 도구의 실무를 채우는 분업이 16년간 이어졌습니다.

Barman은 그 라인업의 중심축이었습니다. 이름은 Backup And Recovery MANager의 머리글자에서 왔습니다.

1.0에서 2.0으로 (2012 → 2016)​

이 시기에 Barman은 SSH 없이도 동작하는 길을 열었습니다.

시점릴리스의미
2012Barman 1.xrsync over SSH 기반 원격 백업, archive_command로 WAL 수집
2016-09-27Barman 2.0streaming-only 백업 도입, SSH 없이 pg_basebackup + replication slot으로 동작
2.x 후반barman-cloud-*S3, Azure, GCS 직결 명령어 도입

Barman 1.x 시대의 모델은 단순했습니다. Barman 서버 한 대가 SSH로 PostgreSQL 서버에 붙어 rsync로 데이터 디렉토리를 가져오고, PostgreSQL의 archive_command가 WAL 파일 하나하나를 Barman 서버로 밀어넣는 구조입니다. 1.x는 PostgreSQL의 백업 절차를 원격에서 자동화했다는 점에서 의미가 있었지만, SSH 의존이라는 단단한 가정이 깔려 있었습니다.

2.0이 그 가정을 풀었습니다. 2016년 9월 27일 발표된 Barman 2.0의 핵심은 다음 한 줄이었습니다.

"Streaming-only backups are now possible through transparent integration with pg_basebackup and full support of replication slots for WAL streaming."

(이제 streaming-only 백업이 가능하다 — pg_basebackup과의 투명한 통합, 그리고 WAL streaming을 위한 replication slot의 전면 지원으로.)

이 변화는 보기보다 컸습니다. SSH 키 분배, 권한 관리, rsync 환경 구성을 매번 짜야 하던 운영자에게, "PostgreSQL의 streaming replication 프로토콜 위에 백업을 얹는다"는 발상은 컨테이너 환경의 PostgreSQL 운영을 비로소 자연스럽게 만들었습니다. 같은 릴리스에서 들어온 synchronous WAL streaming은 RPO=0 백업이라는 개념을 도구 수준에서 처음으로 풀어냈습니다.

이 시점 이후 Barman은 rsync 진영과 streaming 진영을 동시에 지원하는 도구가 됐고, 선택은 운영자에게 맡겨졌습니다. 2.x 후반에 들어온 barman-cloud-* 명령어 계열이 마지막 퍼즐이었습니다. 백업 대상이 로컬 디스크일 수도, S3, Azure, GCS의 오브젝트 스토리지일 수도 있게 됐습니다.

EDB로 향한 2020년 9월의 분기점​

PostgreSQL 생태계에서 2020년 9월 30일은 적지 않은 변곡점입니다.

"EDB has acquired 2ndQuadrant, a global PostgreSQL solutions and tools company based out of the UK." — Crunchbase, 2020-09-30

(EDB가 영국 본사의 글로벌 PostgreSQL 솔루션·도구 회사인 2ndQuadrant를 인수했다.)

EnterpriseDB(EDB, 미국 보스턴)가 2ndQuadrant(영국/이탈리아)를 인수했습니다. 합병 이후 PostgreSQL 코어팀은 별도 성명을 냈습니다. 인수로 코어팀 구성원의 과반이 EDB 소속이 됐고, 한 회사 소속이 코어팀의 50%를 넘지 않는다는 불문율과 충돌했기 때문입니다. 인수가 완료된 2020-10-08의 공식 발표에는 이런 표현이 등장합니다.

"...the largest dedicated provider of PostgreSQL products and solutions worldwide."

(...전 세계 최대 규모의 PostgreSQL 전문 제품·솔루션 제공사)

운영 도구 입장에서 이 사건이 의미하는 바는 분명했습니다. Barman의 메인테이너가 그 시점부터 EDB로 바뀌었습니다. GitHub 저장소도 자연스럽게 EnterpriseDB/barman으로 이관됐습니다. 같은 처지에 놓인 도구로는 BDR(Bi-Directional Replication), repmgr, pglogical 등이 있습니다.

여기서 한 가지 짚어둘 만한 점이 있습니다. "메인테이너가 바뀐다"는 사건의 결과는 동일하지 않습니다. Barman은 EDB 산하에서 활발한 유지를 이어갔습니다. v2.x → v3.x 메이저 전환, 클라우드 백업 강화, 2026년 3월 v3.18.0까지 14년째 정상 궤도에 있습니다.

같은 4월 pgBackRest는 같은 "메인테이너 변경" 변수 앞에서 정반대 결말을 맞았습니다. Crunchy Data의 Snowflake 인수 → 후원 단절 → 종료로 이어졌습니다. 같은 단어가 다른 결말을 낳았습니다. 무엇이 둘을 갈랐는가는 글 끝에서 한 번 다시 짚습니다.

Simon Riggs 이후의 2024년 3월​

이 글에서 한 줄은 따로 두어야 합니다. 2ndQuadrant의 창립자이자 PITR, hot standby, synchronous replication 등을 이끈 PostgreSQL 핵심 개발자였던 Simon Riggs(시몬 리그스)는 2024년 3월 26일 항공 사고로 사망했다.

EDB 인수 이후에도 Riggs는 은퇴할 때까지 EDB에서 Postgres Fellow로 일했습니다. 그가 떠난 자리에 EDB가 — 그리고 Barman이 — 어떤 모양으로 남을지에 대해 적지 않은 사람들이 한 번씩 멈추고 들여다본 시기가 있었습니다. 끝내 도구는 흔들리지 않았습니다.

Bartolini의 추도글이 이 시기를 가장 잘 정리해 둡니다. 16년 전 토스카나의 작은 사무실에 둔 신뢰가, 결국 그가 떠난 뒤에도 도구를 살아 있게 만든 구조였다는 회고입니다.

Barman의 주요 연혁

중앙 카탈로그 모델의 아키텍처​

이제 도구 자체로 들어갑니다. Barman의 구조는 실은 단순합니다.

세 레이어로 쪼개 보면 됩니다.

1. PostgreSQL 서버 (백업 대상)​

하나 또는 여러 대입니다. Barman 입장에서는 등록된 서버마다 별도 카탈로그 항목을 가집니다. 각 서버는 두 채널, base backup 채널과 WAL 채널로 Barman 서버에 연결됩니다.

2. Barman 서버 (중앙 카탈로그)​

Barman의 본체로, 다음을 보관하고 관리합니다.

  • 각 서버의 base backup (rsync 또는 pg_basebackup으로 생성)
  • 각 서버의 WAL archive (streaming 또는 archive_command로 수집)
  • 백업 메타데이터, 보존 정책, 체크 결과
  • 복구 시 사용할 recovery.signal과 설정 파일 자동 생성

설정은 /etc/barman.conf(전역)와 /etc/barman.d/<server>.conf(서버별)로 두 단계로 나뉩니다. 운영 명령어는 단일 진입점 barman 한 줄에 모입니다. barman backup <server>, barman list-backups <server>, barman recover <server> <backup-id> <target>을 사용합니다.

3. Object Storage (선택)​

v2.x 후반부터 들어온 barman-cloud-backup, barman-cloud-wal-archive, barman-cloud-restore 명령어를 통해 S3, Azure Blob, GCS에 직접 업로드, 복원할 수 있습니다. PostgreSQL 서버가 Barman 서버를 거치지 않고 곧바로 클라우드로 백업할 수도, Barman 서버를 거쳐 로컬 + 클라우드 2단 보관을 할 수도 있습니다.

rsync와 streaming, 두 갈래 백업 방식​

Barman의 첫 인상이 "옵션이 많다"라면, 그 인상의 절반은 이 분기 때문입니다.

항목rsync 모델streaming 모델
도입 시기1.x (2012-)2.0 (2016-)
전송 채널SSHPG streaming replication
의존SSH 키, rsync, sudoreplication slot, replication user
증분하드링크 dedup(PG17+ 블록 레벨로 점진 이행)
WAL 수집archive_commandreceive_wal (streaming)
적합 환경베어메탈/VM, SSH 통제 가능컨테이너/K8s, SSH 미허용

어느 쪽이 더 좋다고 단언하긴 어렵습니다. 베어메탈/VM에서 SSH 통제가 명확한 환경이라면 rsync 진영의 하드링크 dedup이 디스크를 덜 먹습니다. 반대로 K8s에서 PostgreSQL을 운영 중이라면 SSH 키 관리 자체가 부담이라 streaming 진영이 자연스럽습니다. CNPG가 Barman Cloud Plugin을 채택한 이유도 거기에 있습니다. PostgreSQL의 streaming 프로토콜 위에 백업을 얹는다는 발상이 K8s의 역할 분리에 잘 맞물립니다.

PostgreSQL에 위임한다는 철학​

pgBackRest 종료 글에서도 인용한 Bartolini의 한 줄이 Barman을 이해하는 가장 짧은 길입니다.

"Barman delegates data copy mechanisms to PostgreSQL primitives and rsync, focusing instead on orchestration, retention, and recovery management."

(Barman은 데이터 복사 메커니즘 자체는 PostgreSQL의 기본 기능과 rsync에 위임하고, 자기는 오케스트레이션·보존·복구 관리에 집중한다.)

이 한 문장을 풀면 이렇습니다. Barman은 "파일을 어떻게 빠르게 복사할 것인가"를 직접 풀지 않습니다. 그건 PostgreSQL의 pg_basebackup과 archive_command, 그리고 rsync가 이미 잘 풀어둔 문제입니다. Barman은 "여러 PostgreSQL 서버의 백업을 어떻게 한 곳에서 카탈로그화하고, 보존 정책을 적용하고, 복구 시점을 결정하고, 명령어 한 줄로 PITR을 실행시킬 것인가"에 집중합니다.

pgBackRest는 정확히 반대 결정을 한 도구였습니다. 복사, 압축, 암호화, 전송을 자체 C 구현체로 끝까지 통제했습니다. 그래서 빨랐고, 그래서 깔끔했고, 그래서 한 사람의 13년이 필요했습니다. Barman은 PostgreSQL 자체의 진화에 올라타는 길을 골랐고, 그 덕에 EDB라는 모회사 변경을 견뎌낸 도구가 됐습니다.

둘 중 어느 쪽이 옳다고 단정할 일은 아닙니다. 다만 "외부 의존을 줄이는 도구"와 "외부 의존에 올라타는 도구"가 메인테이너 변경이라는 변수 앞에서 어떻게 다르게 반응하는지를, 같은 4월의 두 사건이 한 화면에 보여주었습니다.

정리​

항목내용
첫 릴리스2012, 이탈리아 프라토 (2ndQuadrant Italia)
메인테이너 전환2020-10-08 EDB의 2ndQuadrant 인수 완료
백업 방식rsync (1.x~) / streaming (2.0~) / cloud (2.x 후반~), 3가지 모드
최신 릴리스v3.18.0 (2026-03-12)
라이선스GPL-3.0
정체성PG에 복사를 위임하고 오케스트레이션에 집중하는 백업 매니저

Barman이 14년을 조용히 굴러왔다는 사실은, 백업 도구에 기대하는 안정성을 그대로 보여줍니다.

2편에서는 실제로 한 대의 Barman 서버를 세우고, 한 PostgreSQL 인스턴스를 등록해 첫 백업을 떠 보고, 임의의 시점으로 복구하는 5개 명령어 시나리오를 다뤄요. 이 글이 왜 Barman인가에 대한 답이라면, 다음 글은 어떻게 Barman인가에 대한 답이에요.

참고 자료​

pgBackRest 종료와 대안

· 약 11분

2026-05-19 업데이트: 6사 컨소시엄으로 정식 부활했습니다. 자세한 내용은 pgBackRest 컨소시엄 부활에 정리했습니다.

2026-05-04 업데이트: pgBackRest는 사실상 부활 쪽으로 가닥이 잡혔습니다.

이 글을 올리고 며칠 지나지 않아 분위기가 뒤집혔습니다. 저장소 archive가 해제됐고("archived": false), 기본 브랜치가 eol로 바뀐 README 상단에 MAINTENANCE UPDATE 섹션이 새로 추가됐습니다. 그 안에서 David Steele(데이비드 스틸)이 직접 부활 의사를 밝혔습니다.

"It is clear that many pgBackRest users... would prefer the project to continue with me as the primary maintainer. ... This time pgBackRest will be funded by a coalition of sponsors so that a single acquisition will no longer affect my ability to continue work on the project."

(많은 pgBackRest 사용자가 내가 계속 메인테이너로 남아 주기를 바란다는 게 분명해졌다. ... 이번에는 한 회사 매각 한 번으로 프로젝트가 흔들리지 않도록 여러 후원사가 함께 자금을 댄다.)

골자는 세 가지입니다. (1) Steele의 메인테이너 복귀, (2) 단독 후원사(과거 Crunchy Data) 의존 모델을 깨고 sponsor coalition 모델로 전환, (3) 추가 메인테이너 영입 예정입니다. README에 명시된 현재 sponsor는 Supabase입니다. 정식 발표는 같은 주 안에 나올 예정으로 잡혀 있습니다.

본문은 2026-04-27 시점의 충격과 해석을 사료로 남기기 위해 그대로 둡니다. 운영자 입장에서 결론은 단순해졌습니다. 즉각 마이그레이션 압력은 약해졌고, 다음 PostgreSQL 메이저 업그레이드까지의 시간은 그대로입니다. 정식 발표가 나온 뒤 한 번 더 들여다보면 됩니다.

여담 한 줄입니다. 후배가 그러더군요. "이래서 파업들을 하는 거라고." 농담이지만 묘하게 정곡을 찔렀습니다. 13년 일한 사람이 손을 놓고 나서야 비로소 자본이 모였기 때문입니다.

한 사람이 13년 지킨 백업 도구​

PostgreSQL 운영을 좀 해본 사람이라면 pgBackRest라는 이름은 거의 반사적으로 익숙할 텐데요, 전체 / 차등 / 증분 백업, PITR, 병렬 압축/전송, S3, GCS, Azure 직결, 무결성 검증까지 PostgreSQL 백업에 필요한 거의 모든 항목이 한 도구로 깔끔하게 풀리는 사실상의 표준이었어요.

그 표준이 멈췄습니다. 2026년 4월 27일, 단독 메인테이너 David Steele(데이비드 스틸)이 GitHub 저장소를 archive 처리하면서 다음과 같이 공식 안내했습니다.

"After a lot of thought, I have decided to stop working on pgBackRest. I did not come to this decision lightly." — David Steele, pgbackrest GitHub README

13년입니다. 한 사람이 13년 동안 사실상 혼자 유지해 온 도구가 멈췄습니다.

개인적으로는 슬프면서도 "이게 그렇게 단순한 슬픔으로 정리될 일인가" 하는 생각이 들어요. 이 글에서는 사실관계, 다른 사람들의 해석, 대안 도구 비교, CNPG에서 Barman이 어떤 자리에 있는지, 그리고 운영자 입장에서 지금 무엇을 할지 정리합니다.

무슨 일이 있었는가​

먼저 타임라인을 살펴보겠습니다.

시점사건
2013David Steele이 pgBackRest 시작 (Crunchy Data(크런치 데이터) 후원)
2024–2025Crunchy Data 매각, Steele이 후속 직장 / 독립 스폰서십 모색
2026-01-19마지막 릴리스 v2.58.0 ("Object Storage Improvements")
2026-04-27"no longer maintained" 공식 선언, 저장소 archive
2026-04-28Percona 공식 입장: 계속 사용 권장
2026-05-04저장소 archive 해제, README에 MAINTENANCE UPDATE 추가. David Steele 부활 의사 공식화, sponsor coalition 모델로 재구성 중

마지막 릴리스 v2.58.0은 PostgreSQL 18까지 공식 지원합니다. 즉 지금 운영 중인 클러스터의 백업이 당장 멈추는 건 아닙니다. 다만 다음 PostgreSQL 메이저(PostgreSQL 19, 2026-09 예정) 출시 이후 호환성/보안 패치 공급원이 사라진다는 점이 분기점입니다.

왜 이렇게 됐는가​

단독 메인테이너 구조에 함정이 있었습니다.

13년간 코드 리뷰, 릴리스, 이슈 트리아지 거의 전부를 한 사람이 처리한 프로젝트였습니다. Crunchy Data가 Steele의 인건비를 받쳐주는 동안에는 그 모델이 굴러갔습니다. 매각 이후 후속 직장과 독립 후원 시도가 이어졌지만, 어느 쪽도 프로젝트를 제대로 유지할 만큼은 모이지 않았습니다.

pgBackRest 후원 모델 전환

"The person who makes sure your data survives a disaster did not make the cut." — Lætitia Avrot(레티시아 아브로), pgBackRest is dead. Now what?

(데이터 재해 복구를 책임지는 사람의 자리는 결국 잘려나갔다.)

이 한 문장이 사실 사건의 전부입니다. AI 붐을 따라 자본은 GPU와 추론 인프라로 흘러가고, "당신의 데이터가 재해 후에도 살아남게 해주는 도구"의 메인테이너 한 명을 살리는 것은 어느 회사의 우선순위에도 들어가지 못했습니다. Steele 본인은 절제된 톤으로 정리했습니다.

"Rather than do the work poorly and/or sporadically, I think it makes more sense to have a hard stop."

대충 유지하면서 사용자 신뢰만 갉아먹느니 깨끗하게 멈추겠다는 결정입니다. 비난할 자리는 아닙니다. 13년치 코드를 그렇게 마감할 수 있는 사람이 흔하지 않습니다.

같은 사건을 바라보는 네 가지 시선​

며칠 사이에 정리된 PostgreSQL 생태계의 주요 입장을 표로 묶으면 다음과 같습니다.

출처입장 요약권장
Lætitia Avrot(레티시아 아브로) (MyDBA Notebook)"pgBackRest is dead." 신규 평가는 Barman, 기존 사용자는 시간 지날수록 위험 증가Barman 평가 시작
Christophe Pettus(크리스토프 페투스) (thebuild.com)"올바른 결정." OSS 인프라의 자본 부족이 본질적 문제WAL-G가 가장 유망한 대체
Gabriele Bartolini(가브리엘레 바르톨리니) (EDB / CNPG 창립자)"선순환의 단절." Barman과 pgBackRest는 철학적 의견차였다고 평가OSS 지속가능성 모델 자체를 재설계
Percona(페르코나) (공식 블로그)"현 상황은 우리 권장사항에 영향이 없다""Keep on using pgBackRest as you did!"

각 입장이 가리키는 곳이 조금씩 다른데, 그 차이가 오히려 흥미롭습니다. Avrot는 "지금 평가 단계라면 굳이 archived 도구를 신규 도입할 이유가 없다"는 운영자 시점에서 봅니다. Pettus는 "오픈소스 인프라의 본질은 자본 문제이지 기술 문제가 아니다"라고 한 단계 위에서 진단합니다. Bartolini는 자기 진영(Barman/CNPG)의 철학적 정당성을 곱씹으면서도 "선순환이 끊긴 결과"라며 더 큰 그림을 봅니다. Percona는 가장 보수적으로, 자기 고객에게 즉각 마이그레이션을 권하지 않습니다.

네 입장이 모순되지는 않습니다. 시간 축이 다를 뿐입니다. "오늘 당장의 운영"에는 Percona, "다음 분기의 신규 도입"에는 Avrot, "내년의 기술 선택"에는 Pettus, "5년 뒤 OSS가 어떻게 살아남을까"에는 Bartolini가 답합니다.

대안 도구 비교​

운영자 입장에서 결정에 필요한 정보만 압축하면 다음과 같습니다.

도구메인테이너백업 모델PITR증분오브젝트 스토리지비고
Barman(바만)EnterpriseDB (활발)rsync / pg_basebackup 위임지원지원지원 (S3, Azure, GCS)CNPG가 채택. "복사는 PG에 위임, 오케스트레이션에 집중" 철학
WAL-GAiven 등 (활발)스트리밍 + 압축지원지원 (delta)지원클라우드 네이티브, MySQL, SQL Server, MongoDB도 지원
pg_basebackup + pg_combinebackupPG 코어PG 내장수동지원 (PG17+)해당 없음외부 의존 회피, 중소 규모
pgmoneta커뮤니티 (GSoC 활발)내장 + 인크리멘털지원지원부분신생, 검증 데이터 부족
pg_dump / pg_dumpallPG 코어논리 export해당 없음해당 없음해당 없음백업 도구 아님. 마이그레이션/스키마 덤프용

마지막 두 행이 좀 어색해 보이지만, Avrot가 원문에서 한 번 더 짚어준 부분이라 굳이 표에 남겼습니다.

"pg_basebackup is a clone tool, not a backup tool. pg_dump is an export tool."

(pg_basebackup은 클론 도구이지 백업 도구가 아니다. pg_dump는 export 도구다.)

PostgreSQL 입문자가 "PostgreSQL에 기본으로 들어 있는 거 쓰면 되지 않나"라고 생각하기 쉽지만, WAL 관리, 복구 명령, 무결성 검증 중 어느 것도 자체적으로 제공하지 않습니다. pg_basebackup으로 만든 base에 자체 WAL 보존/복구 스크립트를 얹는 방식은 곧 직접 만든 mini-Barman이 되며, 그러느니 진짜 Barman을 쓰는 게 낫습니다.

pg_basebackup + pg_combinebackup (PostgreSQL 17+) 조합은 블록 레벨 증분이 들어왔으므로 외부 도구를 도입하기 싫은 작은 클러스터에는 의미 있는 선택지입니다. 다만 오브젝트 스토리지 직결, 보존 정책, 병렬 복원 같은 운영 편의는 직접 짜야 합니다.

선택은 대체로 둘로 좁혀집니다.

베어메탈/VM 운영, EDB 중심 생태계, K8s 환경(CNPG)에는 Barman이 어울립니다. 멀티 클라우드 / 멀티 DB 엔진을 한 도구로 통일하거나 스트리밍 친화적인 구성을 원한다면 WAL-G가 어울립니다.

CNPG와 Barman​

쿠버네티스에서는 이미 답이 나와 있습니다.

쿠버네티스에서 PostgreSQL을 운영 중이라면 이번 일에 가장 마음 편한 쪽입니다. CloudNativePG(CNPG)가 처음부터 Barman 진영에 서 있었기 때문입니다.

CNPG 공식 문서는 현재 백업 메서드를 세 가지로 명시합니다.

  1. plugin: CNPG-I 플러그인 기반 백업. 공식 권장 경로
  2. volumeSnapshot: Kubernetes CSI 볼륨 스냅샷
  3. barmanObjectStore: Barman Cloud 네이티브 통합. v1.26부터 deprecated

핵심은 1번입니다. 네이티브로 들어 있던 Barman Cloud 통합이 v1.26부터 deprecated 처리됐고, 공식 권장 경로는 Barman Cloud Plugin으로 옮겨갔습니다. 즉 코어와 백업 도구가 분리된 플러그인 구조로 진화 중입니다. pgBackRest는 CNPG 공식 문서에 등장하지 않습니다.

흐름을 그림으로 정리하면 다음과 같습니다.

Bartolini의 글에 이 분리의 철학이 잘 정리돼 있습니다.

"Barman delegates data copy mechanisms to PostgreSQL primitives and rsync, focusing instead on orchestration, retention, and recovery management."

(Barman은 데이터 복사 자체는 PostgreSQL의 기본 기능과 rsync에 위임하고, 자기는 오케스트레이션·보존·복구 관리에 집중한다.)

pgBackRest는 정반대 결정을 한 도구였습니다. 복사 메커니즘을 자기 안에 두고 끝까지 통제한다는 쪽입니다. 둘 중 어느 쪽이 옳다고 단정하긴 어렵습니다. 다만 K8s 오퍼레이터의 역할 분담 모델에는 역할을 잘게 쪼개고 위임하는 Barman 쪽이 잘 맞는다는 점이 결국 드러났습니다.

요약하면 이렇습니다.

  • 베어메탈/VM에서 pgBackRest를 쓰고 있었다면 다음 메이저 업그레이드 사이클에 Barman 또는 WAL-G로의 마이그레이션을 평가합니다.
  • K8s + CNPG 환경이라면 이미 Barman Cloud Plugin이 표준이므로 별도 의사결정이 거의 필요하지 않습니다.

운영자 입장에서 지금 무엇을 할 것인가​

0. 호흡 한 번. 즉각 위험은 없다.
1. 인벤토리 — 어느 클러스터가 pgBackRest를 쓰는지
2. 다음 PostgreSQL 메이저 업그레이드 일정 = 분기점
3. 신규 클러스터 / 평가 단계는 Barman 또는 WAL-G로
4. K8s라면 CNPG + Barman Cloud Plugin
5. 단독 메인테이너 의존도가 높은 도구를 식별
6. 백업이 아니라 복구 — 정기 복구 리허설

조금 풀어 쓰면 다음과 같습니다.

  1. 즉시 교체할 필요는 없습니다. v2.58.0은 PostgreSQL 18까지 공식 지원하고, Percona가 "계속 쓰라"는 공식 입장을 냈습니다. 패닉 마이그레이션은 추가 사고를 만듭니다.
  2. 분기점은 다음 PostgreSQL 메이저 업그레이드 시점. PostgreSQL 19 이후 호환성/보안 이슈가 생겼을 때 아무도 책임지지 않습니다. 그 시점까지 마이그레이션 계획만 잡아두면 충분합니다.
  3. 신규로 도입한다면 archived 도구를 새로 들이는 건 합리적이지 않습니다. Avrot의 권고대로 Barman부터 평가합니다.
  4. K8s + CNPG 환경에서는 사실상 의사결정이 끝났습니다. Barman Cloud Plugin으로 가면 됩니다.
  5. 이번 사건의 일반화된 교훈. 운영 critical path에 단독 메인테이너 의존 도구가 또 어디에 있는지 한 번 점검해 볼 가치가 있습니다. 같은 일이 다른 도구에서 다시 일어나지 말란 법이 없습니다.
  6. 백업이 아니라 복구가 본질입니다. 어느 도구로 옮기든, 옮긴 직후 복구 리허설을 한 번 돌려보지 않으면 실제로는 백업이 없는 것과 같습니다.

오픈소스 인프라의 자본 문제에 대한 단상​

Bartolini는 글에서 "선순환(virtuous cycle)"이라는 표현을 썼습니다.

"Companies invest in engineers who build open-source software. That software creates production value. Those organizations purchase commercial support. Companies reinvest profits into engineering."

(회사가 엔지니어에 투자해 OSS를 만든다 → OSS가 운영 가치를 만든다 → 그 운영 가치가 상용 지원으로 환원된다 → 회사가 다시 엔지니어에 재투자한다.)

이 사이클은 어느 한 고리만 끊겨도 무너집니다. pgBackRest는 Crunchy Data의 후원이라는 한 고리에 매달려 있다가, 그 회사의 매각이라는 외생 변수 한 번에 끊겼습니다. 그 단계에서 커뮤니티 후원이 빈자리를 못 메웠다는 게 사건의 본질입니다.

오픈소스가 공짜라는 건 사용자 입장에서의 이야기입니다. 누군가는 그 비용을 치르고 있고, 그 비용 청구서가 어디에도 도착하지 않으면 결국 한 사람이 13년을 혼자 지키다가 손을 놓게 됩니다. 한 사람이 13년을 지킨 코드는 그 자체로 이미 자본 부족을 증명합니다.

Pettus의 한 줄이 이 단상을 닫기에 가장 정확합니다.

"This is not a problem with a technical solution. In the long run, it is the only important problem."

(이건 기술적 해법이 있는 문제가 아니다. 장기적으로 보면, 사실 그것만이 유일하게 중요한 문제다.)

정리​

어제까지오늘부터
pgBackRest 상태사실상 표준archived (v2.58.0 동결)
단독 메인테이너David Steele 13년(미정, 포크 가능성 있음)
후원 모델Crunchy Data 단독 (~2024)후원 부재 → (2026-05-04) sponsor coalition 재구성 중 (현재: Supabase)
신규 평가 권장pgBackRestBarman (또는 WAL-G), 정식 발표 후 재평가
K8s 환경다양한 옵션 검토 가능CNPG + Barman Cloud Plugin
즉각 위험해당 없음없음 (다음 메이저 업그레이드까지)
장기 위험해당 없음PG 신버전 호환성/보안 패치 공급 단절

같은 일이 다음에 일어날 도구는 어디일까요. 그 질문이 이번 사건이 우리에게 남긴 진짜 숙제예요.

참고 자료​

PostgreSQL 18 비동기 I/O

· 약 10분

30년 가까이 동기 I/O로 버텨온 데이터베이스​

PostgreSQL은 1996년에 첫 릴리스가 나온 이래로 process-per-connection 모델 + 동기 I/O라는 단순한 조합을 30년 가까이 유지해왔어요. 클라이언트 하나가 붙으면 백엔드 프로세스 하나가 fork되고, 그 프로세스는 디스크에서 페이지를 읽을 때 read() 시스템 콜을 직접 호출해서 결과가 돌아올 때까지 그냥 멈춰서 기다려요.

이 모델은 단순함이 가장 큰 무기였습니다. fork 한 번이면 격리되고 신호 처리도 직관적이며, 잠금이나 컨텍스트 스위치 같은 까다로운 동시성 이슈도 적게 신경 써도 됩니다. MySQL이나 SQL Server가 thread-per-connection 모델로 가면서 동시성 버그와 평생을 싸우는 동안, PostgreSQL은 다른 길을 갔습니다.

문제는 2020년대의 NVMe와 클라우드 스토리지가 그 단순함의 대가를 점점 더 비싸게 만들고 있다는 점입니다.

PostgreSQL 18(2025-09-25 GA)은 약 30년 만에 처음으로 비동기 I/O 서브시스템을 들고 왔습니다. 이 글에서는 그 구조와 트레이드오프를 살펴봅니다.

동기 I/O의 정확한 병목​

PostgreSQL이 디스크에서 데이터를 읽을 때의 기본 단위는 8KB 페이지 한 장입니다. Sequential Scan 한 번에 100만 페이지를 읽어야 한다면, 옛 모델은 이렇게 동작합니다.

Sequential Scan (cold cache):

read(page 1) → 디스크 응답 대기 (lat) → 처리
read(page 2) → 디스크 응답 대기 (lat) → 처리
read(page 3) → 디스크 응답 대기 (lat) → 처리
...

총 시간 ≈ 페이지 수 × 디스크 latency (직렬)

CPU는 매 페이지마다 디스크를 기다리며 놀고, NVMe는 큐가 대부분 비어 있는 채로 능력의 일부만 씁니다. 클라우드 EBS처럼 한 IOP의 latency가 수백 마이크로초인 환경에서는 이 직렬 누적이 잔인하게 드러납니다.

PostgreSQL도 손 놓고 있던 건 아닙니다. 17 버전까지는 OS의 readahead와 posix_fadvise(POSIX_FADV_WILLNEED) 정도로 커널에 "앞으로 이 영역을 읽을 테니 미리 좀 가져와"라고 힌트만 줬습니다. 동작은 하지만, PostgreSQL이 직접 I/O 깊이를 통제하지 못하기 때문에 어떤 페이지가 언제 도착할지를 알 수 없었습니다. 통계 기반 readahead는 패턴이 깨지면 무력해집니다.

"This feature allows backends to queue multiple read requests, which allows for more efficient sequential scans, bitmap heap scans, vacuums, etc." — PostgreSQL 18 Release Notes, E.4.3.1.3

PostgreSQL 18의 한 줄 요지는 이렇습니다. "여러 read를 큐에 넣고 동시에 보냅니다." 페이지마다 멈추지 않고 100개를 한꺼번에 던져놓은 뒤, NVMe가 알아서 병렬로 처리하게 두자는 이야기입니다.

세 가지 io_method​

PostgreSQL 18은 새 GUC 파라미터 io_method로 비동기 I/O 동작을 고릅니다. 세 가지 옵션이 있습니다.

모드어디서 동작환경 요건장점단점
sync메인 백엔드모든 OSPG17과 동일, 호환성 100%, 추가 프로세스 없음사실상 비동기 X, NVMe/EBS 큐 활용 못 함
workerI/O 워커 프로세스모든 OS (default)어디서든 돌고 sync 대비 1.6배, 운영 안정성 검증된 디폴트워커 프로세스 비용, 컨텍스트 스위치/메모리 카피 오버헤드
io_uring커널과 ring buffer 공유Linux 5.1+sync 대비 2.7배, 워커 없이 syscall 거의 0일부 배포판/컨테이너에서 seccomp으로 차단, Linux 전용(macOS/BSD 불가)

기본값은 worker입니다. 모든 운영체제에서 동작하면서도 동기보다는 확연히 빠르기 때문에 안전한 선택입니다. macOS, BSD 또는 io_uring을 못 쓰는 환경에서는 그대로 default로 두면 됩니다.

함께 추가된 GUC들도 알아둘 만합니다(공식 release notes).

io_workers는 worker 모드의 워커 프로세스 수이며 기본값은 3입니다. io_combine_limit와 io_max_combine_limit는 인접 read를 한 요청으로 합치는 한도입니다. effective_io_concurrency의 기본값은 1 → 16으로 올랐으며, 이 값은 사실상 "동시에 던질 read 수"입니다. maintenance_io_concurrency는 VACUUM 같은 유지보수 작업의 동시 I/O를 조절합니다.

fadvise()가 없는 OS에서도 effective_io_concurrency > 0이 의미를 갖게 됐다는 점도 조용히 큰 변화입니다. 이전에는 Linux 외 환경에서 이 파라미터가 사실상 장식이었습니다.

worker 모드​

누군가 대신 디스크를 읽는 방식입니다.

worker의 구조는 의외로 깔끔합니다.

백엔드는 read 요청을 큐에 넣고 다른 일을 합니다. 별도의 I/O 워커 프로세스 풀이 큐에서 꺼내 실제 read() syscall을 호출하고, 결과를 공유 버퍼에 채워줍니다. 백엔드는 자기가 요청한 페이지가 필요한 시점에 공유 버퍼만 들여다보면 됩니다.

장점은 호환성입니다. POSIX read만 있으면 어디서든 동작합니다. 단점은 워커 프로세스 자체가 일종의 디스패처라서 컨텍스트 스위치와 메모리 카피 비용이 발생한다는 점입니다.

io_uring 모드​

커널에 ring을 심는 방식입니다. io_uring은 Linux 5.1(2019년)에 들어온 커널 비동기 I/O 인터페이스입니다. PostgreSQL과 커널 사이에 공유 메모리 ring 두 개를 두고, 시스템 콜 없이 요청을 주고받습니다.

PostgreSQL 18 구현의 흥미로운 결정 하나는 ring 인스턴스를 backend마다 따로 둔다는 점입니다. 한 인스턴스를 여러 백엔드가 공유하면 lock contention이 생기므로 아예 격리했습니다. 다만 ring 자체는 fork 전에 postmaster가 미리 생성해서 shared memory에 올려둡니다(credativ deep-dive).

워커가 빠지므로 컨텍스트 스위치가 사라지고, syscall도 제출과 대기를 잘 묶으면 거의 0에 수렴합니다. 다만 Linux 5.1+ 전용이며, 일부 배포판은 보안 정책상 io_uring을 비활성화해두기도 합니다(예: 특정 컨테이너 런타임의 seccomp 프로파일).

pg_aios​

PostgreSQL의 I/O 큐를 처음으로 들여다볼 수 있게 됐습니다. 운영자 입장에서 더 반가운 변화는 새 시스템 뷰 하나입니다. pg_aios는 진행 중인 비동기 I/O 요청을 그대로 보여줍니다.

SELECT pid, io_id, state, operation, target, off, length
FROM pg_aios
ORDER BY pid, off
LIMIT 20;

지금까지 PostgreSQL의 I/O는 거의 블랙박스였습니다. pg_stat_io(PostgreSQL 16에서 들어옴)는 누적 통계를, pg_stat_activity는 wait event 정도를 보여줬습니다. "바로 지금 어떤 read가 큐에 떠 있는가"는 이제야 들여다볼 수 있게 됐습니다.

여기서 잡히는 정보로 특정 파일에 I/O가 몰리는 hot relation을 식별하거나, io_uring이 큐를 정말 깊게 쓰고 있는지를 확인할 수 있습니다.

벤치마크​

숫자로 성능 차이를 살펴봅니다.

pganalyze 벤치마크는 AWS c7i.8xlarge에서 3.5GB 테이블의 cold scan을 측정했습니다.

버전 / 모드실행 시간대 PG17대 PG18 sync
PostgreSQL 17 (sync)15,830 ms기준해당 없음
PostgreSQL 18 sync15,071 ms-5%기준
PostgreSQL 18 worker10,051 ms-37%-33%
PostgreSQL 18 io_uring5,723 ms-64%-62%

PostgreSQL 17과 18 I/O 모드별 cold scan 실행 시간 비교

cold cache Sequential Scan에서 io_uring은 PostgreSQL 17 대비 2.7배 빠릅니다. worker도 1.6배 빠릅니다. 같은 PostgreSQL 18을 sync로만 켜두면 거의 차이가 없다는 점도 중요한 신호입니다. 모드 선택이 곧 성능입니다.

이 향상이 어디서 오는지 한 줄로 정리하면, 디스크 latency를 여러 번 직렬로 치르던 것을 한 번 병렬로 치르게 된 것뿐입니다. NVMe는 원래 그렇게 쓰는 물건이었는데, PostgreSQL이 약 30년 만에 그 사용법을 익혔습니다.

한계​

아직 비동기가 아닌 작업도 있습니다.

PostgreSQL 18 AIO는 읽기 작업 일부에만 적용됩니다.

작업PG18 AIO 적용?
Sequential Scan적용
Bitmap Heap Scan적용
VACUUM적용
ANALYZE (일부)적용
Index Scan random read아직 동기
WAL write미적용
Checkpoint write미적용

쓰기는 전부 동기 그대로입니다. 인덱스 random read도 들어가지 않았습니다. 즉 OLTP 점 쿼리 워크로드는 이번 변화로 직접 빨라지지는 않고, 이득은 분석 워크로드/대량 스캔/VACUUM에 몰려 있습니다.

PostgreSQL 19(2026-09-24 Beta 4 공지 기준 GA는 10월 예상) 개발 트리에서는 인덱스 prefetch와 일부 쓰기 경로의 비동기화가 논의되고 있습니다. AIO는 PostgreSQL 18에서 끝난 게 아니라 시작된 것에 가깝습니다.

운영 관점​

무엇을 켤지 선택하는 기준은 사실 단순합니다.

환경추천
Linux 5.1+ 직접 운영, io_uring 사용 가능io_uring
Linux지만 io_uring 비활성 배포판/컨테이너worker (default)
macOS / BSD / 구형 Linuxworker (default)
호환성 이슈 발생 시 임시 폴백sync

추가로 함께 조정할 만한 설정은 다음과 같습니다.

# postgresql.conf 예시 (분석 워크로드 기준)
io_method = io_uring
effective_io_concurrency = 32 # 기본 16에서 NVMe 깊이에 맞게 조정
maintenance_io_concurrency = 32 # VACUUM 가속
io_combine_limit = 256kB

effective_io_concurrency는 "동시에 던질 read 수"라고 생각하면 됩니다. NVMe 큐 깊이 / 동시 사용자 수에 맞춰 늘립니다. 너무 키우면 다른 백엔드와 디스크 대역을 놓고 다투게 되니, 분석 전용 인스턴스가 아니라면 기본 16에서 천천히 올립니다.

배포 전에는 다음 항목을 확인합니다.

  1. 커널 버전(uname -r): 5.1 미만이면 io_uring 불가
  2. seccomp/AppArmor 프로파일이 io_uring 시스템 콜을 막는지 확인
  3. pg_aios 뷰가 보이는지로 AIO 활성 검증
  4. cold scan 워크로드에서 EXPLAIN (ANALYZE, BUFFERS) 비교 측정

한국 커뮤니티 반응​

GeekNews에는 이미 작년에 Postgres 18을 기다리며: 비동기 I/O로 디스크 읽기 속도 향상이 올라왔습니다. 댓글은 많지 않지만 톤은 거의 "드디어"에 가깝고, 관심사는 io_uring 보안 우려와 클라우드 NVMe에서의 측정 향상에 집중돼 있었습니다. 1년이 지난 지금 PostgreSQL 18이 GA된 상태에서 그 기대가 어느 정도 채워졌다고 봐도 됩니다.

정리​

PG17 이하PG18
I/O 모델동기 (메인 백엔드 직접 syscall)동기 + worker + io_uring
모드 선택없음io_method GUC
가시성pg_stat_io (누적 통계)+ pg_aios (실시간 큐)
effective_io_concurrencyLinux fadvise 한정모든 OS, 기본 16
Cold scan 성능 (3.5GB)15.8초5.7초 (io_uring)
적용 범위—Sequential/Bitmap Scan, VACUUM
미적용—Index random read, WAL/Checkpoint write

PostgreSQL이 process-per-connection이라는 30년 가까이 이어온 아키텍처 결정을 바꾸지 않고서도 비동기 I/O를 들였다는 점이 이번 변화의 진짜 핵심입니다. 백엔드 프로세스 모델은 그대로 두고 syscall 경계만 다시 그었습니다. 그래서 운영자 입장에서 마이그레이션이 거의 무료이며, io_method 한 줄만 바꾸면 됩니다.

다음 PostgreSQL 18 시리즈 글에서는 UUIDv7과 B-tree 인덱스의 관계를 다룰 예정이에요. 같은 "스토리지 효율"이라는 축에서, 이번에는 인덱스 페이지 분할 쪽 이야기를 해보려고 해요.

참고 자료​

PG19 파티션 MERGE/SPLIT

· 약 5분
2026-09-28 업데이트

ALTER TABLE ... MERGE PARTITIONS와 SPLIT PARTITIONS는 2026-09-24 PostgreSQL 19 Beta 4 공지에서 revert됐습니다. PostgreSQL 19 정식 버전에는 이 문법이 들어가지 않습니다. 아래 본문의 PostgreSQL 문법 설명은 revert 전 개발 버전 기준 기록입니다. 경위와 컨테이너 비교 결과는 PG19 Beta 4 기능 철회에 정리했습니다.

PostgreSQL 파티셔닝의 짧은 역사​

PostgreSQL의 파티셔닝은 10 버전에서 선언적(declarative) 문법이 들어온 이후 꾸준히 성숙해왔어요. 11에서 UPDATE로 파티션 간 row 이동이 가능해졌고, 14에서 DETACH PARTITION CONCURRENTLY가 추가됐어요. 그리고 PostgreSQL 19에서 마침내 파티션 자체를 병합하고 분할하는 DDL이 들어와요.

ALTER TABLE ... SPLIT PARTITION ...
ALTER TABLE ... MERGE PARTITIONS ...

익숙한 문법입니다. Oracle을 써본 사람이라면 20년 넘게 봐온 구문과 거의 판박입니다. 다만 자세히 보면 문법과 제약에서 결정적인 차이들이 있습니다.

이전까지 PostgreSQL에서는 어떻게 했나​

PostgreSQL 18 이하에서 파티션을 병합하려면 이런 절차를 거쳤습니다.

  1. 새로운 합쳐진 파티션 테이블을 생성
  2. 기존 파티션에서 INSERT SELECT로 데이터 이동
  3. 기존 파티션 DETACH
  4. 새 파티션 ATTACH
  5. 기존 파티션 DROP

한 번에 트랜잭션으로 묶기도 까다롭고, 데이터 이동 중 ACCESS EXCLUSIVE LOCK이 걸려 서비스 가용성에도 영향을 줍니다. 분할도 마찬가지로 귀찮았습니다. Oracle에서는 한 줄이면 끝나는 작업이었습니다.

PostgreSQL 19의 새 문법​

SPLIT과 MERGE를 각각 예제로 살펴보겠습니다.

SPLIT PARTITION​

-- 원본 파티션 테이블
CREATE TABLE sales (
id bigint generated always as identity,
region text not null,
amount numeric
) PARTITION BY LIST (region);

CREATE TABLE sales_all PARTITION OF sales
FOR VALUES IN ('KR', 'JP', 'CN', 'US', 'UK');

-- 하나의 파티션을 셋으로 분할
ALTER TABLE sales SPLIT PARTITION sales_all INTO (
PARTITION sales_asia FOR VALUES IN ('KR', 'JP', 'CN'),
PARTITION sales_us FOR VALUES IN ('US'),
PARTITION sales_uk FOR VALUES IN ('UK')
);

MERGE PARTITIONS​

-- 세 파티션을 하나로 합치기
ALTER TABLE sales MERGE PARTITIONS
(sales_asia, sales_us, sales_uk)
INTO sales_all;

PostgreSQL 19의 파티션 분할과 병합 구조

문법에서 눈여겨볼 점은 다음과 같습니다.

  • 지원 타입: RANGE, LIST (HASH는 지원하지 않음)
  • RANGE 파티션은 인접(adjacent) 해야 병합 가능
  • LIST는 인접 제약 없음
  • 소스 파티션들은 괄호 (...)로 감싼다
  • 대상 파티션에 PARTITION 키워드를 붙이지 않는다

Oracle의 문법​

같은 일을 Oracle에서 하면 이렇게 됩니다. Oracle 쪽은 8i 시절부터 존재해온 문법이라 변주가 많습니다.

SPLIT PARTITION​

-- RANGE 파티션 분할 (AT 값 기준)
ALTER TABLE sales SPLIT PARTITION sales_2026 AT (DATE '2026-07-01')
INTO (
PARTITION sales_2026_h1 TABLESPACE ts1,
PARTITION sales_2026_h2 TABLESPACE ts2
);

-- LIST 파티션 분할 (VALUES 기준)
ALTER TABLE sales SPLIT PARTITION sales_all
VALUES ('KR', 'JP', 'CN')
INTO (
PARTITION sales_asia,
PARTITION sales_rest
);

MERGE PARTITIONS​

-- 기본 문법
ALTER TABLE sales MERGE PARTITIONS
sales_q1_2026, sales_q2_2026, sales_q3_2026, sales_q4_2026
INTO PARTITION sales_2026;

-- RANGE 전용 TO 단축 문법
ALTER TABLE sales MERGE PARTITIONS
sales_q1_2026 TO sales_q4_2026
INTO PARTITION sales_2026;

나란히 비교​

문법 차이​

항목PostgreSQL 19Oracle
MERGE 소스 지정MERGE PARTITIONS (p1, p2) 괄호MERGE PARTITIONS p1, p2 나열
MERGE 대상 지정INTO p_newINTO PARTITION p_new
SPLIT 경계 지정FOR VALUES IN (...) / FOR VALUES FROM ... TO ...AT (value) (RANGE) / VALUES (...) (LIST)
RANGE 범위 병합 단축없음 (일일이 나열)p1 TO p4 단축 문법
TABLESPACE 지정각 대상 파티션마다 개별동일

기능/제약 차이​

항목PostgreSQL 19Oracle
지원 파티션 타입RANGE, LISTRANGE, LIST, SYSTEM
HASH 파티션지원 안 함지원 안 함
RANGE 인접 조건필요필요
LIST 인접 조건불필요불필요
락 수준ACCESS EXCLUSIVE (전 구간)EXCLUSIVE + ONLINE 옵션 (EE, SPLIT 12.2+, MERGE 18c+)
실행 방식단일 프로세스병렬 실행 가능

실전에서 갈리는 락과 온라인 실행​

문법은 거의 맞춰졌습니다. 하지만 운영에서 진짜 갈리는 것은 락과 온라인 실행 여부입니다.

PostgreSQL 19​

공식 커밋 메시지와 depesz의 벤치(1천만 행 LIST 파티션 기준)에서 확인된 내용은 다음과 같습니다.

  • 작업 전체 구간에서 ACCESS EXCLUSIVE LOCK 유지
  • 단일 프로세스에서 순차 실행
  • 대규모 파티션에서는 실질적 다운타임이 발생할 수 있음

쓸모 있는 평가는 이렇습니다. "편의성은 크게 좋아졌지만, 온라인성은 Oracle을 따라잡지 못했습니다." 한 줄 SQL로 간단히 표현할 수 있게 된 것만으로도 의미가 크지만, 수백 GB 파티션을 무중단으로 합칠 수 있는 수준은 아닙니다.

Oracle​

Oracle Enterprise Edition에서는 ONLINE 키워드로 DML 블로킹 없이 파티션을 쪼개거나 합칠 수 있습니다. SPLIT PARTITION ... ONLINE은 12.2부터, MERGE PARTITIONS ... ONLINE은 18c부터 지원합니다.

ALTER TABLE sales MERGE PARTITIONS
sales_q1_2026, sales_q2_2026
INTO PARTITION sales_h1_2026
ONLINE;

내부적으로는 shadow 구조를 만들고 점진적으로 데이터를 이관하는 방식이라, 배타 락 보유 시간이 거의 없습니다. Enterprise Edition 라이선스가 전제라는 게 큰 단서지만, 대형 운영 환경에서 체감되는 차이는 큽니다.

PostgreSQL 19와 Oracle 온라인 파티션 작업의 락 구간 비교

어디까지 따라왔고, 어디까지 아직인가​

따라잡은 것

  • SPLIT/MERGE DDL 자체의 존재: SQL 한 줄로 끝납니다
  • 지원 파티션 타입 (RANGE, LIST)
  • 인접 조건 정책 (RANGE는 인접 필요, LIST는 무관)
  • 다중 분할(한 파티션 → N개)과 다중 병합(N개 → 하나)

아직 못 따라온 것

  • 온라인(ONLINE) 실행: 대형 파티션은 실질적 다운타임이 남음
  • 병렬 실행: 단일 프로세스로만 처리
  • RANGE 범위 병합 단축 문법(p1 TO p4) 없음

PostgreSQL의 역사적 약점 중 하나가 "큰 파티션 테이블의 유지보수가 DDL 한 줄로 안 끝난다"였습니다. PostgreSQL 19는 그 거리를 눈에 띄게 좁혔습니다. 다만 운영에서 가장 아픈 "락을 오래 붙잡는다"는 문제는 남아 있어서, 대형 테이블은 여전히 DETACH CONCURRENTLY + 수동 작업 전략을 병용해야 할 수 있습니다.

Oracle에서 PostgreSQL로 넘어올 때 "제가 쓰던 그 문법, PostgreSQL에도 있어요?"라는 질문의 목록이 하나씩 채워지고 있어요. 이번은 파티션 병합/분할 차례였어요.


참고

PG19 EXPLAIN RDTSC

· 약 6분

EXPLAIN ANALYZE의 불편한 진실​

EXPLAIN ANALYZE는 PostgreSQL에서 쿼리 성능을 분석할 때 가장 먼저 쓰는 도구예요. 실제로 쿼리를 실행하면서 각 노드의 실행 시간, 행 수, 루프 횟수를 보여줘요.

그런데 한 가지 문제가 있습니다. EXPLAIN ANALYZE를 붙이는 것 자체가 쿼리를 느리게 만듭니다.

이건 버그가 아니라 구조적인 문제입니다. 공식 문서에서도 이렇게 경고합니다.

"The measurement overhead added by EXPLAIN ANALYZE can be significant, especially on machines with slow gettimeofday() operating-system calls."

왜 느려지나​

EXPLAIN ANALYZE는 실행 계획의 각 노드를 통과할 때마다 시간을 측정합니다. 구체적으로는 InstrStartNode(시작)과 InstrStopNode(종료)에서 시스템 시계를 읽습니다.

핵심은 이 시계를 읽는 함수가 clock_gettime()이라는 것입니다.

쿼리 실행 흐름:

SeqScan (100만 행)
├─ 행 1: clock_gettime() → 처리 → clock_gettime()
├─ 행 2: clock_gettime() → 처리 → clock_gettime()
├─ 행 3: clock_gettime() → 처리 → clock_gettime()
│ ...
└─ 행 1,000,000: clock_gettime() → 처리 → clock_gettime()

→ clock_gettime() 호출 횟수: 200만 번

100만 행을 스캔하면 clock_gettime()이 200만 번 호출됩니다. 중첩된 노드가 있으면 더 늘어납니다. 각 호출이 약 20ns라고 해도, 200만 번이면 40ms의 순수 오버헤드가 발생합니다.

프로파일링을 해보면 InstrStartNode/InstrStopNode의 실행 시간 대부분이 clock_gettime()에서 소비됩니다. 실제 쿼리 로직이 아니라 시간 측정에 시간을 쓰고 있는 것입니다.

clock_gettime()이 뭔데​

clock_gettime()은 Linux에서 고해상도 시간을 가져오는 시스템 콜입니다. 정확하고 안정적이지만, 호출할 때마다 비용이 듭니다.

현대 Linux에서는 VDSO (Virtual Dynamic Shared Object)를 통해 커널 진입 없이 호출할 수 있도록 최적화되어 있습니다. 그래도 호출당 약 18~20ns의 오버헤드가 있습니다. 플랫폼에 따라 20~100ns까지 달라질 수 있습니다.

clock_gettime() 호출 경로:

사용자 공간 → VDSO → TSC 레지스터 읽기 → 보정 → 반환
↑
커널 진입 없이 실행되지만
보정 로직 때문에 여전히 비용 발생

매번 이 경로를 거치는 게 문제입니다. 쿼리 노드 처리 시간이 수십 ns 수준이면, 시간 측정 비용이 실제 처리 비용보다 클 수도 있습니다.

TIMING FALSE라는 우회로​

사실 PostgreSQL 9.2부터 이 문제의 우회 방법은 있었습니다.

EXPLAIN (ANALYZE, TIMING FALSE) SELECT * FROM large_table;

TIMING FALSE를 쓰면 시간 측정을 건너뜁니다. 실제 행 수와 루프 횟수만 보여줍니다. 오버헤드가 거의 사라집니다.

하지만 타이밍이 없으면 어느 노드가 병목인지 알 수 없습니다. 그래서 실무에서는 결국 TIMING TRUE(기본값)를 쓰게 됩니다.

pg_test_timing 유틸리티로 현재 시스템의 타이밍 오버헤드를 확인합니다.

$ pg_test_timing
Testing timing overhead for 3 seconds.
Per loop time including overhead: 18.80 ns

PostgreSQL 19의 해결책: RDTSC​

PostgreSQL 19에서는 x86-64 CPU의 RDTSC (Read Time-Stamp Counter) 명령어를 활용합니다.

RDTSC란​

CPU에는 TSC(Time Stamp Counter)라는 카운터가 있습니다. CPU 클럭마다 1씩 증가하는 레지스터입니다. RDTSC 명령어는 이 카운터 값을 직접 읽습니다.

clock_gettime(): 사용자 공간 → VDSO → 보정 로직 → 반환
RDTSC: CPU 레지스터 직접 읽기 → 반환

EXPLAIN ANALYZE의 시스템 시계와 RDTSC 읽기 경로 비교

시스템 콜도, VDSO 경유도, 보정 로직도 없습니다. CPU 명령어 하나로 끝납니다.

성능 비교​

pg_test_timing으로 측정한 결과:

클럭 소스평균 루프 시간비교
System clock (기존)18.80 ns기준
RDTSC (PG19)11.69 ns38% 감소
RDTSCP16.94 ns10% 감소

200만 번 호출 기준으로 환산하면:

클럭 소스총 오버헤드
System clock~37.6 ms
RDTSC~23.4 ms
절감량~14.2 ms

단일 쿼리에서 14ms라면 크지 않아 보이지만, 중첩 노드가 있거나 행 수가 더 많으면 오버헤드는 행 수와 노드 수에 비례해 커집니다.

RDTSC vs RDTSCP​

두 명령어의 차이를 알아두면 좋습니다.

RDTSCRDTSCP
속도더 빠름 (11.69 ns)약간 느림 (16.94 ns)
순서 보장비순차 실행 가능순서 보장됨
정밀도약간 낮음높음
용도EXPLAIN ANALYZE (상대적 시간 측정)절대적 시간 측정이 필요한 경우

RDTSC는 CPU의 비순차 실행(out-of-order execution)으로 인해 측정 순서가 살짝 뒤바뀔 수 있습니다. 하지만 EXPLAIN ANALYZE에서는 상대적인 시간 차이만 보면 되므로, 약간의 부정확함은 문제가 되지 않습니다.

PostgreSQL 19는 EXPLAIN ANALYZE에는 빠른 RDTSC를, 높은 정밀도가 필요한 다른 경우에는 RDTSCP를 사용합니다.

timing_clock_source 설정​

새로운 timing_clock_source 파라미터로 클럭 소스를 제어합니다. 허용값은 auto, system, tsc 세 가지이고 기본값은 auto입니다. superuser만 바꿀 수 있습니다.

-- 현재 설정 확인
SHOW timing_clock_source;

-- 변경 (postgresql.conf 또는 SET, superuser 전용)
SET timing_clock_source = 'auto'; -- 기본값. 지원 CPU면 TSC, 아니면 OS 시스템 클럭
SET timing_clock_source = 'tsc'; -- CPU 명령어(x86-64의 RDTSC/RDTSCP)로 측정
SET timing_clock_source = 'system'; -- OS 시스템 클럭 (기존 방식)

tsc를 선택해도 RDTSC와 RDTSCP 중 무엇을 쓸지는 PostgreSQL이 용도에 따라 정합니다. 두 명령어를 따로 고르는 값은 없습니다.

x86-64 CPU에서 해당 명령어를 지원하면 auto가 TSC 클럭 소스를 선택합니다. 오래된 x86-64 CPU나 ARM 등 다른 아키텍처에서는 기존 방식이 유지됩니다. 공식 문서는 쿼리 실행 중 이 값을 바꾸면 구간 시간이 크게 튀거나 음수가 나올 수 있다고 경고합니다.

주의사항​

x86-64 전용​

이번 최적화는 x86-64 아키텍처 전용입니다. ARM 기반 서버(AWS Graviton 등)에서는 적용되지 않습니다. 향후 ARM용 최적화도 추가될 수 있지만, 초기 릴리즈에는 포함되지 않았습니다.

TSC 신뢰성​

모든 x86-64 CPU에서 TSC가 동일하게 동작하지는 않습니다. 오래된 CPU나 가상화 환경에서는 TSC가 불안정할 수 있습니다. PostgreSQL은 TSC 지원 여부를 확인한 후 자동으로 적절한 클럭 소스를 선택합니다.

변화의 체감​

일상적인 EXPLAIN ANALYZE 사용에서 이 변화를 극적으로 체감하기는 어려울 수 있습니다. 하지만 대량 행을 처리하는 복잡한 쿼리에서는 측정 오버헤드 감소가 실행 시간 측정의 정확도를 높여준다는 점이 핵심입니다.

역사: 6년간의 논의​

이 아이디어가 처음 나온 건 2020년입니다. Andres Freund(안드레스 프로인트)가 PostgreSQL Hackers 메일링 리스트에 "Reduce timing overhead of EXPLAIN ANALYZE using rdtsc?" 라는 제목으로 제안했습니다.

6년 동안 논의와 구현이 이어진 끝에 PostgreSQL 19에 드디어 포함되었습니다. 아이디어 자체는 단순하지만, TSC 안정성 검증, 다양한 CPU/VM 환경 호환성, 정밀도 트레이드오프 같은 세부 사항을 해결하는 데 시간이 걸렸습니다.

정리​

기존 (PG18 이하)PG19
클럭 소스clock_gettime()RDTSC
호출당 비용~18.80 ns~11.69 ns
개선율-38% 감소
대상 아키텍처모든 플랫폼x86-64 (자동 감지)
설정없음timing_clock_source

EXPLAIN ANALYZE를 프로덕션에서 쓰는 건 여전히 주의가 필요해요. 하지만 PostgreSQL 19부터는 측정 때문에 발생하는 노이즈가 줄어들어, 더 정확한 성능 분석이 가능해져요.

참고 자료​

PostgreSQL 19 새 기능 총정리

· 약 6분
2026-09-28 업데이트

2026-09-24 PostgreSQL 19 Beta 4 공지에서 이 글이 소개한 기능 일부가 revert됐습니다. 파티션 MERGE/SPLIT, SQL/PGQ가 여기에 해당합니다. 공지에는 이 밖에 online data checksums, FOR PORTION OF, pg_get_role_ddl() 등 DDL 함수의 revert와 제거도 있습니다. GROUP BY ALL은 그보다 앞서 Beta 3 이전에 revert됐습니다. 공지 기준 GA 예상 시점도 10월로 바뀌었습니다. 경위와 컨테이너 비교 결과는 PG19 Beta 4 기능 철회에 정리했습니다.

PostgreSQL 19 개요​

PostgreSQL 19는 2026년 9월 출시 예정이에요. 기능 동결(Feature Freeze)은 4월 8일에 완료되었고, 5월에 첫 번째 베타가 나올 예정이에요.

PostgreSQL 18이 비동기 I/O, UUIDv7, 가상 생성 컬럼 같은 굵직한 기능을 가져왔다면, PostgreSQL 19는 기존 기능의 실질적 개선과 성능 최적화에 집중하는 릴리즈입니다. 화려하진 않지만 실무에서 체감할 수 있는 변화가 많습니다.

이 글은 2026년 4월 기준으로 작성되었으며, 정식 출시 전까지 일부 내용이 변경될 수 있습니다.

업데이트: 첫 베타(Beta 1)는 2026-06-04에 나왔습니다. 아래 GROUP BY ALL은 Beta 3에서 revert되어 최종 릴리스에서 빠졌습니다.

쿼리 플래너 & 실행​

GROUP BY ALL​

SELECT department, role, COUNT(*)
FROM employees
GROUP BY ALL;

GROUP BY ALL을 쓰면 집계 함수가 아닌 SELECT 표현식을 자동으로 GROUP BY에 포함합니다. 컬럼 5개를 일일이 나열할 필요가 없어졌습니다.

Eager Aggregation​

대용량 테이블을 조인하기 전에 먼저 집계하는 최적화입니다. enable_eager_aggregate 파라미터로 제어합니다.

-- 플래너가 자동으로 orders를 먼저 집계한 후 customers와 조인
SELECT c.name, SUM(o.amount)
FROM customers c JOIN orders o ON c.id = o.customer_id
GROUP BY c.name;

조인 대상 행 수가 줄어들기 때문에 대용량 분석 쿼리에서 성능이 크게 개선될 수 있습니다.

COUNT() 자동 최적화​

COUNT(1), COUNT(not_null_col)을 자동으로 COUNT(*)로 변환합니다. 습관적으로 COUNT(1)을 쓰던 사람들에게는 반가운 변화입니다.

병렬 TID Range Scan​

TID Range Scan이 병렬 실행을 지원합니다. 대규모 테이블에서 특정 범위의 물리적 행을 빠르게 스캔할 수 있습니다.

Incremental Sort 확장​

Append/MergeAppend 노드 내에서도 Incremental Sort가 가능해졌습니다. 파티션 테이블의 정렬 성능이 개선됩니다.

EXPLAIN ANALYZE 타이밍 오버헤드 감소​

x86-64 CPU에서 RDTSC 명령어를 활용해 시간 측정 오버헤드를 약 38% 줄였습니다. 이 주제는 별도 포스트에서 상세히 다룹니다.

윈도우 함수 개선​

IGNORE NULLS / RESPECT NULLS​

lag, lead, first_value, last_value, nth_value에서 NULL 처리 방식 지정을 지원합니다.

SELECT
ts,
value,
last_value(value) IGNORE NULLS OVER (ORDER BY ts) AS last_non_null
FROM sensor_data;

시계열 데이터에서 NULL 값을 건너뛰고 마지막 유효 값을 가져오는 패턴이 훨씬 간결해졌습니다. 이전에는 서브쿼리나 래터럴 조인이 필요했습니다.

파티셔닝​

MERGE / SPLIT PARTITIONS​

-- 파티션 병합
ALTER TABLE bookings MERGE PARTITIONS (p_2024_q1, p_2024_q2) INTO p_2024_h1;

-- 파티션 분할
ALTER TABLE bookings SPLIT PARTITION p_2024 INTO
(PARTITION p_2024_h1 FOR VALUES FROM ('2024-01-01') TO ('2024-07-01'),
PARTITION p_2024_h2 FOR VALUES FROM ('2024-07-01') TO ('2025-01-01'));

파티션을 병합하거나 분할하는 DDL이 추가되었습니다. 기존에는 새 파티션을 만들고 데이터를 이동한 뒤 기존 파티션을 삭제해야 했습니다.

COPY TO with Partitioned Tables​

파티션 테이블에서 직접 COPY TO가 가능해졌습니다. 이전에는 각 파티션을 개별적으로 복사해야 했습니다.

논리 복제 (Logical Replication)​

WAL 레벨 동적 조정​

서버 재시작 없이 WAL 레벨 변경을 지원합니다. effective_wal_level 읽기 전용 파라미터로 현재 상태를 확인합니다.

이전에는 wal_level = logical 설정 변경 후 반드시 재시작이 필요했습니다. 운영 중인 시스템에서 논리 복제를 활성화할 때 다운타임을 줄일 수 있습니다.

시퀀스 복제​

논리 복제에서 시퀀스 값 동기화를 지원합니다.

ALTER SUBSCRIPTION my_sub REFRESH SEQUENCES;

WAIT FOR LSN​

레플리카가 특정 LSN까지 동기화될 때까지 대기하는 메커니즘입니다. 읽기 일관성이 필요한 경우에 유용합니다.

동기화 모니터링​

pg_replication_slots에 slotsync_skip_reason 컬럼이 추가되어 슬롯 동기화 지연 원인을 파악하기 쉬워졌습니다.

성능 최적화​

jsonb_agg 성능 향상​

jsonb_agg 함수의 성능이 50% 이상 개선되었습니다. JSON 집계가 많은 API 백엔드에는 반가운 변화입니다.

LISTEN/NOTIFY 최적화​

채널별 해시 테이블 기반으로 처리 방식이 변경되어, 많은 채널을 사용하는 환경에서 성능이 개선됩니다.

ICU 문자 변환 최적화​

UTF-8 데이터베이스에서 ICU 문자 변환 함수의 성능이 향상되었습니다.

Buffer Cache 알고리즘 변경​

Free buffer list를 Clock-sweep 알고리즘으로 교체했습니다.

Temporary Table 트렁케이션 고속화​

임시 테이블 truncation 처리가 빨라졌습니다.

모니터링 & 관찰성​

pg_stat_statements 확장​

  • Generic/Custom 플랜별 호출 횟수 추적
  • FETCH 명령어 정규화
  • IN 절 파라미터 리스트 정규화

VACUUM / ANALYZE 진행 상황​

  • mode, started_by 컬럼 추가로 수동/자동 실행 구분 가능
  • VACUUM VERBOSE에서 메모리 사용량 표시
  • vacuumdb --dry-run 옵션 추가
  • log_autoanalyze_min_duration 신규 파라미터로 ANALYZE 로깅 분리

pg_buffercache 확장​

pg_buffercache_os_pages가 OS 페이지 분포를 보여줍니다.

WAL 모니터링​

wal_fpi_bytes로 Full Page Image 바이트를 추적합니다.

멀티트랜잭션 통계​

pg_get_multixact_stats() 함수가 멀티트랜잭션 사용 현황을 보여줍니다.

SQL & 함수​

random() 날짜/시간 생성​

SELECT random('2024-01-01'::date, '2024-12-31'::date);

지정 범위 내 랜덤 날짜/시간을 생성합니다. 테스트 데이터 생성이 간편해졌습니다.

base64url 인코딩​

encode/decode 함수에서 URL-safe한 base64url 포맷을 지원합니다.

error_on_null()​

NULL 값 검증 함수가 추가되었습니다. NULL이 들어오면 에러를 발생시킵니다.

SRF(Set-Returning Function) 인라인​

Set-returning 함수의 인라인 최적화가 지원됩니다.

관리 & 도구​

regdatabase 타입​

데이터베이스 이름과 OID를 상호 변환하는 새 객체 식별자 타입입니다.

CHECKPOINT 개선​

FLUSH_UNLOGGED, MODE 파라미터가 추가되었습니다.

pg_upgrade 대용량 객체 최적화​

대용량 객체(Large Object) 마이그레이션이 빨라졌습니다.

psql 개선​

  • %S 프롬프트 옵션으로 검색 경로 표시
  • boolean 값 표시 커스터마이징 (\pset display_true)

pgbench​

--continue-on-error 옵션으로 에러 발생 시에도 벤치마크를 계속 진행합니다.

그래프 쿼리 (SQL/PGQ)​

PostgreSQL 19는 SQL:2023 Part 16(SQL/PGQ) 을 코어에 들였습니다. 관계형 테이블 위에 프로퍼티 그래프 뷰를 얹고, GRAPH_TABLE과 MATCH 구문으로 그래프 패턴 매칭을 표준 SQL 안에서 풉니다. 별도 그래프 DB나 확장 없이 PostgreSQL만으로 일부 그래프 워크로드를 받아낼 수 있게 되었습니다.

첫 구현은 고정 깊이 패턴까지입니다. 가변 길이 경로는 다음 릴리스의 1차 후보입니다. 이 주제는 분량이 커서 별도 포스트에서 상세히 다룹니다.

호환성 주의​

standard_conforming_strings 비활성화 불가​

이제 standard_conforming_strings를 off로 설정할 수 없습니다. 비표준 문자열 리터럴(\' 등)은 에러를 발생시킵니다. escape_string_warning 파라미터도 제거되었습니다.

기존에 E'...' 없이 역슬래시 이스케이프를 쓰던 레거시 코드가 있다면 확인이 필요합니다.

log_lock_waits 기본값 변경​

log_lock_waits가 기본적으로 활성화됩니다. 잠금 대기 로그가 자동으로 남습니다.

마무리​

PostgreSQL 19는 눈에 띄는 신기능보다는 기존 기능의 완성도를 높이는 릴리즈입니다.

제가 개인적으로 주목하는 기능은 분석 쿼리 성능에 직접 영향을 주는 Eager Aggregation과 운영 중 논리 복제 활성화를 편하게 해주는 WAL 레벨 동적 조정이에요. IGNORE NULLS는 시계열 처리를 깔끔하게 해주고, 표준 그래프 쿼리를 코어에 들인 SQL/PGQ도 눈에 들어와요. EXPLAIN ANALYZE RDTSC는 다음 글에서, SQL/PGQ는 후속편에서 상세히 다룹니다.

2026년 9월 정식 출시가 기대되고, 5월 베타부터 미리 테스트해볼 수 있어요.

참고 자료​