EC2에서 직접 굴리는 PostgreSQL의 백업 스크립트를 손보다가, 그동안 pg_basebackup으로 매번 전체본을 통째로 떠오던 게 눈에 걸렸다. DB가 수백 GB로 커지면 이 전체 백업 한 번이 스토리지와 시간을 크게 잡아먹는다. 그래서 예전에는 pgBackRest 같은 외부 도구를 붙여 증분을 뜨곤 했는데, PostgreSQL 17부터는 이 기능이 코어 안으로 들어왔다. 이번 글은 그 네이티브 증분 백업을 실제로 돌려본 기록이다.
정리부터 하자면, PostgreSQL 17의 증분 백업은 서버에서 summarize_wal을 켜 두고, pg_basebackup --incremental=이전_backup_manifest로 이전 백업 이후 바뀐 블록만 떠서 용량과 시간을 줄이는 방식이다. 증분본은 그 자체로는 복구할 수 없고, pg_combinebackup으로 전체 백업과 합쳐 복구용 전체본을 만들어야 데이터 디렉터리로 쓸 수 있다.
1. PostgreSQL 17에 증분 백업이 들어온 배경
증분 백업은 직전 백업 이후에 바뀐 데이터 블록만 저장하는 백업이다. 전체 백업이 모든 파일을 그대로 복사하는 것과 달리, 변경분만 담으므로 백업 크기가 작고 완료도 빠르다. 대신 복구할 때 전체본과 증분본을 다시 합쳐야 한다.
여기서 핵심 장치가 WAL 요약(WAL summary)이다. WAL은 PostgreSQL이 모든 변경을 먼저 기록하는 트랜잭션 로그인데, PostgreSQL 17은 이 WAL을 훑어 "어느 릴레이션의 어느 블록이 바뀌었는지"를 요약 파일로 만들어 pg_wal/summaries 디렉터리에 쌓아 둔다. 이 일을 하는 백그라운드 프로세스가 WAL summarizer다. 증분 백업을 요청하면 서버는 이 요약을 참고해 바뀐 블록만 골라 보낸다.
공식 문서는 이 점을 분명히 한다. 필요한 요약 파일이 없으면 증분 백업 시도는 실패한다. "요약 파일은 직전 백업의 시작 LSN부터 이번 백업의 시작 LSN까지의 모든 LSN을 덮어야 한다"는 게 요구 조건이다. LSN은 WAL 상의 위치를 가리키는 로그 시퀀스 번호다.
2. 서버 준비: summarize_wal 켜기
WAL summarizer는 기본으로 꺼져 있다. summarize_wal의 기본값이 off라서, 이걸 켜지 않으면 요약 파일이 쌓이지 않고 증분 백업도 안 된다. 다행히 이 파라미터는 sighup 컨텍스트라 재시작 없이 설정 리로드만으로 적용된다.
sarc=# ALTER SYSTEM SET summarize_wal = on; ALTER SYSTEM sarc=# SELECT pg_reload_conf(); pg_reload_conf ---------------- t (1 row)
적용됐는지 확인한다.
sarc=# SHOW summarize_wal; summarize_wal --------------- on (1 row)
WAL summarizer 프로세스가 떴는지도 확인해 두면 깔끔하다.
$ ps -ef | grep 'walsummarizer' | grep -v grep postgres 4123 4110 0 10:02 ? 00:00:00 postgres: walsummarizer
주의할 조건이 하나 있다. wal_level이 minimal이면 summarize_wal을 on으로 둬도 summarizer가 요약 파일을 만들지 않는다. 증분 백업을 쓸 서버라면 wal_level은 replica 이상이어야 한다.
요약 파일 보관 기간도 같이 챙긴다. wal_summary_keep_time의 기본값은 10일이다. 이 값은 전체 백업과 그에 딸린 증분 백업 사이에 벌어질 수 있는 시간보다 넉넉하게 잡아야 한다. 요약이 먼저 지워지면 증분 백업이 실패하기 때문이다. 주 단위로 전체를 뜨고 매일 증분을 뜬다면 10일도 빠듯할 수 있으니 여유 있게 늘려 둔다.
sarc=# ALTER SYSTEM SET wal_summary_keep_time = '30d'; ALTER SYSTEM sarc=# SELECT pg_reload_conf();
3. 기준이 될 전체 백업 한 번 뜨기
증분은 항상 어떤 기준 백업을 참조한다. 그 기준의 시작점이 전체 백업이다. 평소대로 pg_basebackup으로 전체본을 뜬다. 버전 번호는 저장소 상태에 따라 다를 수 있다.
$ pg_basebackup --version pg_basebackup (PostgreSQL) 17.5
$ pg_basebackup -h 127.0.0.1 -U repl -D /backup/full_0901 -c fast -P 512000/512000 kB (100%), 1/1 tablespace
백업 디렉터리 안에 backup_manifest가 생겼는지 본다. 이 매니페스트가 다음 증분 백업의 기준 파일이 된다.
$ ls /backup/full_0901/backup_manifest /backup/full_0901/backup_manifest
매니페스트는 WAL 파일을 뺀 백업 내 모든 파일의 크기, 마지막 수정 시각, 체크섬을 담는다. --no-manifest로 생성을 끌 수도 있지만, 그러면 이 백업을 기준으로 삼는 증분을 못 뜬다. 증분 체인의 출발점이 될 전체 백업에서는 매니페스트를 끄지 마라.
4. 증분 백업 뜨기
이제 변경분만 뜬다. -i(또는 --incremental)에 직전 백업의 backup_manifest 경로를 넘긴다. 이 매니페스트는 서버로 업로드되고, 서버는 그 이후 바뀐 블록만 담은 증분 백업을 돌려준다.
$ pg_basebackup -h 127.0.0.1 -U repl \
-D /backup/incr_0902 \
--incremental=/backup/full_0901/backup_manifest \
-c fast -P
41216/41216 kB (100%), 1/1 tablespace
전체본이 500MB대였는데 증분본은 수십 MB 수준으로 떨어지는 걸 볼 수 있다. 실제 절감폭은 그날 얼마나 많은 블록이 바뀌었느냐에 달려 있어 환경에 따라 다르지만, 변경이 적은 날일수록 증분본은 작아진다.
여기서 한 번 걸리기 쉽다. summarizer가 아직 최근 WAL을 요약하지 못했거나 필요한 요약이 이미 지워졌으면 다음처럼 실패한다.
pg_basebackup: error: could not initiate base backup: ERROR: WAL summaries are required on timeline 1 from ... but no summaries for that timeline and LSN range exist
서버는 없는 요약 파일이 나타날 때까지 기다려 주기도 한다. summarizer가 뒤처진 경우라면 잠시 뒤 다시 시도하면 된다. 하지만 wal_summary_keep_time이 지나 요약이 삭제됐다면 그 기준으로는 더 이상 증분을 못 뜬다. 이때는 전체 백업부터 다시 떠야 한다.
다음 증분을 또 뜰 때는 기준을 방금 뜬 증분본의 매니페스트로 바꾼다. 이렇게 하면 전체 하나에 증분이 사슬처럼 이어지는 체인이 된다.
$ pg_basebackup -h 127.0.0.1 -U repl \
-D /backup/incr_0903 \
--incremental=/backup/incr_0902/backup_manifest \
-c fast -P
5. pg_combinebackup으로 복구용 전체본 합성하기
증분본은 그대로는 복구에 쓸 수 없다. 문서에도 못을 박아 뒀다. 증분 백업은 직접 복원할 수 없으며 pg_combinebackup으로 앞선 백업들과 합쳐야 한다. 이 도구는 전체본과 그 뒤로 이어진 증분본들을 받아 하나의 합성 전체본(synthetic full backup)을 만들어 낸다.
인자로 넘기는 백업 디렉터리 순서가 중요하다. 오래된 것부터 최신 순으로, 즉 전체본을 맨 앞에, 복원하려는 마지막 증분본을 맨 뒤에 둔다. 출력 위치는 -o로 지정하며 이 옵션은 필수다.
$ pg_combinebackup \
/backup/full_0901 \
/backup/incr_0902 \
/backup/incr_0903 \
-o /restore/pgdata_0903
실행 전에 어떤 작업이 벌어질지 미리 보고 싶으면 -n(dry-run)을 붙인다. 파일을 만들지 않고 예정된 동작만 출력한다.
$ pg_combinebackup -n \
/backup/full_0901 /backup/incr_0902 /backup/incr_0903 \
-o /restore/pgdata_0903
결과로 나온 /restore/pgdata_0903은 완전한 데이터 디렉터리다. 여기에 알맞은 postgresql.conf와 복구 설정을 얹고 인스턴스를 띄우면 그 시점의 DB가 올라온다. 이 합성 전체본을 다시 다음 pg_combinebackup의 입력으로 넣을 수도 있다.
체인 무결성은 반드시 지켜야 한다. 중간 증분본을 하나라도 빼면 복원은 실패한다. pg_combinebackup은 넘긴 백업들이 올바른 체인을 이루는지는 확인하지만, 각 백업 파일이 손상 없이 온전한지까지 검사하지는 않는다.
6. 백업 무결성은 pg_verifybackup으로 따로 검사
합성이 체인의 논리적 정합성만 본다면, 파일 자체가 멀쩡한지는 pg_verifybackup이 매니페스트의 체크섬과 대조해 확인한다. 백업을 떠 둔 직후, 그리고 복원 직전에 한 번씩 돌려 두면 마음이 놓인다.
$ pg_verifybackup /backup/full_0901 backup successfully verified
증분본도 같은 방식으로 검사할 수 있다. 각 백업 디렉터리를 개별로 검증하면 된다.
7. 실무에서 걸리는 지점들
RDS에서는 이 방법을 못 쓴다. 이 절차는 EC2 등에서 직접 운영하는 PostgreSQL을 대상으로 한다. Amazon RDS나 Aurora는 관리형이라 서버 파일시스템과 슈퍼유저 접근을 열어 주지 않으므로 pg_basebackup으로 base backup을 뜨는 방식이 아니라 자체 스냅샷과 PITR을 쓴다. 네이티브 증분 백업은 서버를 스스로 굴릴 때의 선택지다.
체크섬 상태를 바꿨다면 새 전체본을 떠라. pg_combinebackup은 페이지 체크섬을 다시 계산하지 않는다. 체크섬을 켜거나 끄는 변경 뒤에 옛 체크섬 상태의 백업을 섞어 합성하면 결과물에 잘못된 체크섬이 남을 수 있다. 이럴 때는 증분을 이어 붙이지 말고 전체 백업을 새로 뜨는 게 맞다.
요약 보관 기간과 백업 주기를 맞춰라. 4번에서 본 실패는 대부분 wal_summary_keep_time이 백업 간격보다 짧아서 생긴다. 전체를 뜨는 주기, 증분을 뜨는 주기, 요약 보관 기간을 한 축에 놓고 요약이 항상 살아 있도록 잡는다.
테이블스페이스가 있으면 -T로 재배치한다. 원본과 다른 경로에 복원할 때 pg_combinebackup -T 옛경로=새경로로 테이블스페이스 위치를 옮긴다.
정리
PostgreSQL 17의 증분 백업은 외부 도구 없이 코어만으로 백업 시간과 용량을 줄인다. 순서는 간단하다. 서버에서 summarize_wal을 켜고 wal_summary_keep_time을 백업 주기에 맞춰 잡은 뒤, pg_basebackup으로 전체본을 한 번 뜨고, 이후에는 직전 백업의 backup_manifest를 --incremental에 넘겨 변경분만 뜬다. 복원할 때는 pg_combinebackup으로 전체본과 증분본들을 오래된 순서대로 합쳐 전체본을 만들고, pg_verifybackup으로 무결성을 확인한다. RDS가 아닌 EC2 자체 운영 환경이라면 지금 백업 스크립트에 바로 얹어 볼 만하다.