연속 아카이빙과 특정 시점 복구
연속 아카이빙과 특정 시점 복구 (Continuous Archiving & PITR)
백업의 궁극적인 질문은 "마지막 백업 이후의 데이터는 어떻게 지키지?"예요. 파일 시스템 백업은 통째로 복사하니 간단하지만, 일반적으로 백업 시점 이후의 변경분은 잃어버리게 돼요. PostgreSQL은 이 문제를 연속 아카이빙(continuous archiving) 으로 풀어요. WAL(Write-Ahead Log) 파일을 계속 쌓아두면, 파일 시스템 백업과 WAL 재생을 결합해 장애 시점 직전까지, 심지어 원하는 임의의 시점까지 데이터를 복구할 수 있어요. 이 방식을 PITR(Point-In-Time Recovery)라고 불러요. 이 챕터는 그 전체 절차를 다루는데, 분량이 있으니 천천히 따라와 보세요.
출처: 공식문서
왜 이 방식이 필요한가
PostgreSQL은 항상 클러스터 데이터 디렉터리의 pg_wal/ 하위 디렉터리에 WAL(WAL)을 유지해요. WAL은 데이터베이스 데이터 파일에 대한 모든 변경을 기록해요. 이 로그는 주로 크래시 안전성을 위해 존재해요. 시스템이 크래시하면 마지막 체크포인트 이후의 로그 항목을 '재생(replay)'해서 데이터베이스를 일관 상태로 복원할 수 있거든요. 그런데 이 로그의 존재 덕분에 백업의 세 번째 전략이 가능해졌어요. 파일 시스템 수준 백업과 WAL 파일 백업을 결합하는 거예요. 복구가 필요하면 파일 시스템 백업을 복원한 다음, 백업해 둔 WAL 파일을 재생해 시스템을 최신 상태로 가져오는 거죠.
이 방식은 앞선 두 접근보다 관리가 복잡하지만, 상당한 이점이 있어요.
- 완벽히 일관된 파일 시스템 백업이 시작점으로 필요 없어요. 백업의 내부 불일치는 로그 재생으로 바로잡혀요(크래시 복구 때 일어나는 것과 크게 다르지 않아요). 그래서 파일 시스템 스냅샷 기능이 필요 없고, tar나 비슷한 아카이빙 도구만 있으면 돼요.
- 재생을 위해 무한히 긴 WAL 파일 시퀀스를 결합할 수 있으므로, WAL 파일을 계속 아카이빙하기만 하면 연속 백업이 돼요. 전체 백업을 자주 뜨기 어려운 큰 데이터베이스에서 특히 값져요.
- WAL 항목을 끝까지 재생할 필요는 없어요. 어느 지점에서든 재생을 멈추면 그 시점의 일관된 데이터베이스 스냅샷을 얻어요. 그래서 이 기법은 특정 시점 복구(PITR) 를 지원해요. 베이스 백업 이후의 아무 시점으로든 데이터베이스를 복원할 수 있어요.
- WAL 파일 시퀀스를 같은 베이스 백업이 로드된 다른 머신에 계속 보내면 그게 바로 웜 스탠바이(warm standby) 시스템이 돼요. 언제든 두 번째 머신을 띄우면 거의 최신 데이터베이스 사본을 갖게 되죠.
참고:
pg_dump와pg_dumpall은 파일 시스템 수준 백업을 만들지 않으며 연속 아카이빙 솔루션의 일부로 쓸 수 없어요. 그런 덤프는 논리적이고 WAL 재생에 쓸 충분한 정보를 담고 있지 않아요.
일반 파일 시스템 백업 기법처럼, 이 방법도 데이터베이스 클러스터 전체의 복원만 지원하지 일부는 지원하지 않아요. 또 아카이브 저장 공간을 많이 요구해요. 베이스 백업이 부피가 클 수 있고, 바쁜 시스템은 아카이빙해야 할 수 MB의 WAL 트래픽을 만들어내죠. 그래도 높은 신뢰성이 필요한 상황에서는 선호되는 백업 기법이에요.
연속 아카이빙으로 성공적으로 복구하려면, 백업 시작 시점까지 거슬러 올라가는 연속된 아카이브 WAL 파일 시퀀스가 필요해요. 그래서 첫 베이스 백업을 뜨기 전에 WAL 파일 아카이빙 절차를 설정하고 테스트해야 해요.
WAL 아카이빙 설정하기
추상적으로 보면 실행 중인 PostgreSQL 시스템은 무한히 긴 WAL 레코드 시퀀스를 만들어내요. 시스템은 이 시퀀스를 물리적으로 WAL 세그먼트 파일로 나누는데, 보통 각각 16MB예요(세그먼트 크기는 initdb 때 바꿀 수 있어요). 세그먼트 파일은 추상 WAL 시퀀스에서의 위치를 반영하는 숫자 이름을 가져요. WAL 아카이빙을 쓰지 않을 때 시스템은 보통 몇 개의 세그먼트 파일만 만들고, 더 이상 필요 없는 세그먼트 파일을 더 높은 세그먼트 번호로 이름을 바꿔 '재활용(recycle)'해요. 마지막 체크포인트보다 이전 내용을 가진 세그먼트 파일은 더 이상 관심 대상이 아니라고 보고 재활용할 수 있다고 가정해요.
WAL 데이터를 아카이빙할 때는 각 세그먼트 파일이 채워지면 그 내용을 포착해서, 세그먼트 파일이 재활용되기 전에 어딘가에 저장해야 해요. '어딘가에 저장하는' 방법은 애플리케이션과 하드웨어에 따라 정말 다양해요. 다른 머신의 NFS 마운트 디렉터리로 복사하거나, 테이프 드라이브에 쓰거나(각 파일의 원래 이름을 식별할 방법을 확보해야 해요), 배치로 묶어 CD에 구워 넣을 수도 있어요. PostgreSQL은 관리자에게 유연성을 주기 위해 아카이빙이 어떻게 이루어질지 가정하지 않아요. 대신 관리자가 쉘 명령이나 아카이브 라이브러리를 지정해서, 완성된 세그먼트 파일을 필요한 곳으로 복사하게 해요. cp를 쓰는 간단한 쉘 명령일 수도, 복잡한 C 함수일 수도 있어요 — 전적으로 우리한테 달려 있어요.
WAL 아카이빙을 켜려면 wal_level 설정 매개변수를 replica 이상으로, archive_mode를 on으로 설정하고, archive_command 설정 매개변수에 쓸 쉘 명령을 지정하거나 archive_library 설정 매개변수에 쓸 라이브러리를 지정해요. 실제로 이 설정들은 항상 postgresql.conf 파일에 두게 돼요.
archive_command에서 %p는 아카이빙할 파일의 경로명으로, %f는 파일 이름으로만 치환돼요. (경로명은 현재 작업 디렉터리, 즉 클러스터의 데이터 디렉터리에 상대적이에요.) 명령에 실제 % 문자를 넣어야 하면 %%를 써요. 가장 단순한 유용한 명령은 이래요.
archive_command = 'test ! -f /mnt/server/archivedir/%f && cp %p /mnt/server/archivedir/%f' # Unix
archive_command = 'copy "%p" "C:\\server\\archivedir\\%f"' # Windows
이 명령은 아카이빙 가능한 WAL 세그먼트를 /mnt/server/archivedir 디렉터리로 복사해요. (%p와 %f 매개변수가 치환된 뒤 실행되는 실제 명령은 이렇게 보일 수 있어요.)
test ! -f /mnt/server/archivedir/00000001000000A900000065 && cp pg_wal/00000001000000A900000065 /mnt/server/archivedir/00000001000000A900000065
아카이빙할 새 파일마다 비슷한 명령이 생성돼요.
아카이브 명령은 PostgreSQL 서버가 실행되는 것과 같은 사용자의 소유로 실행돼요. 아카이빙되는 WAL 파일 시퀀스는 사실상 데이터베이스의 모든 것을 담고 있으므로, 아카이브 데이터를 남의 눈으로부터 보호하고 싶을 거예요. 예를 들어 그룹이나 세계 읽기 권한이 없는 디렉터리에 아카이빙하세요.
아카이브 명령이 성공할 때만 0 종료 상태를 반환하는 것이 중요해요. 0 결과를 받으면 PostgreSQL은 파일이 성공적으로 아카이빙됐다고 가정하고 그 파일을 제거하거나 재활용해요. 반면 0이 아닌 상태는 파일이 아카이빙되지 않았음을 알려주고, 성공할 때까지 주기적으로 다시 시도해요.
아카이빙하는 또 다른 방법은 사용자 정의 아카이브 모듈을 archive_library로 쓰는 거예요. 이런 모듈은 C로 작성되므로 직접 만들려면 쉘 명령보다 훨씬 많은 노력이 들어갈 수 있어요. 하지만 아카이브 모듈은 쉘을 통한 아카이빙보다 성능이 좋을 수 있고, 유용한 서버 리소스에 접근할 수 있어요.
아카이브 명령이 (서버 종료의 일부로 쓰이는 SIGTERM 외의) 신호로 종료되거나, 쉘이 125보다 큰 종료 상태(예: command not found)로 오류를 내거나, 아카이브 함수가 ERROR나 FATAL을 내면, 아카이버 프로세스는 중단되고 postmaster에 의해 재시작돼요. 이런 경우 실패는 pg_stat_archiver에 보고되지 않아요.
아카이브 명령과 라이브러리는 일반적으로 기존 아카이브 파일을 덮어쓰지 않도록 설계해야 해요. 이건 관리자 오류(예: 두 서로 다른 서버의 출력을 같은 아카이브 디렉터리로 보내는 경우)로부터 아카이브 무결성을 지키는 중요한 안전 기능이에요. 제안한 아카이브 라이브러리가 기존 파일을 덮어쓰지 않는지 테스트하는 게 좋아요.
드물게 PostgreSQL이 이전에 아카이빙한 WAL 파일을 다시 아카이빙하려 할 수 있어요. 예를 들어 시스템이 아카이빙 성공의 내구성 있는 기록을 남기기 전에 크래시하면, 서버는 재시작 후 그 파일을 다시 아카이빙하려 해요(아카이빙이 여전히 켜져 있다면요). 아카이브 명령·라이브러리가 기존 파일을 만나면, WAL 파일 내용이 기존 아카이브와 동일하고 기존 아카이브가 저장소에 완전히 영속되었으면 0 상태(또는 true)를 반환해야 해요. 기존 파일이 아카이빙 중인 WAL 파일과 다른 내용을 담고 있으면 0이 아닌 상태(또는 false)를 반환해야 해요.
위의 Unix 예시 명령은 별도의 test 단계를 포함해 기존 아카이브 덮어쓰기를 피해요. 일부 Unix 플랫폼에서 cp에는 -i 같은 스위치가 있어 같은 일을 덜 장황하게 할 수 있지만, 올바른 종료 상태가 반환되는지 확인하지 않고 그걸 신뢰하면 안 돼요. (특히 GNU cp는 -i를 쓰고 대상 파일이 이미 존재할 때 종료 상태 0을 반환하는데, 이는 바람직한 동작이 아니에요.)
아카이빙 설정을 설계할 때, 어떤 측면에 운영자 개입이 필요하거나 아카이브 공간이 부족해서 아카이브 명령·라이브러리가 반복적으로 실패하면 어떻게 될지 고려해보세요. 예를 들어 오토체인저 없는 테이프에 쓰는 경우, 테이프가 차면 테이프를 교체할 때까지 더는 아카이빙할 수 없어요. 어떤 오류 조건이나 운영자 요청도 적절히 보고되어 상황이 합리적으로 빨리 해결되게 해야 해요. 상황이 해결될 때까지 pg_wal/ 디렉터리는 WAL 세그먼트 파일로 계속 채워져요. (pg_wal/를 담은 파일 시스템이 가득 차면 PostgreSQL은 PANIC 종료를 해요. 커밋된 트랜잭션은 잃지 않지만, 공간을 비울 때까지 데이터베이스는 오프라인 상태로 남아요.)
아카이브 명령·라이브러리의 속도는 서버가 WAL 데이터를 생성하는 평균 속도를 따라잡을 수만 있으면 중요하지 않아요. 아카이빙 프로세스가 조금 뒤처져도 정상 운영은 계속돼요. 아카이빙이 크게 뒤처지면 재해 시 잃을 데이터 양이 늘어나요. 또 pg_wal/ 디렉터리에 아직 아카이빙되지 않은 세그먼트 파일이 많아져 결국 디스크 공간을 초과할 수도 있어요. 아카이빙 프로세스가 의도한 대로 동작하는지 모니터링하는 게 좋아요.
아카이브 명령·라이브러리를 작성할 때, 아카이빙할 파일 이름은 최대 64자까지 가능하고 ASCII 문자·숫자·점의 어떤 조합이든 담을 수 있다고 가정해야 해요. 원래 상대 경로(%p)를 보존할 필요는 없지만 파일 이름(%f)은 보존해야 해요.
WAL 아카이빙이 데이터베이스의 데이터 변경 복원을 허용하지만, 설정 파일(postgresql.conf, pg_hba.conf, pg_ident.conf)의 변경은 복원하지 않는다는 점을 기억하세요. 그것들은 SQL 연산이 아니라 수동으로 편집되기 때문이에요. 설정 파일을 일반 파일 시스템 백업 절차로 백업되는 위치에 두고 싶을 거예요.
아카이브 명령·함수는 완성된 WAL 세그먼트에서만 호출돼요. 그래서 서버가 WAL 트래픽을 거의 만들지 않으면(또는 그러한 한가한 구간이 있으면), 트랜잭션 완료와 아카이브 저장소에 안전히 기록되는 사이에 긴 지연이 있을 수 있어요. 아카이빙되지 않은 데이터가 얼마나 오래될 수 있는지 제한하려면 archive_timeout을 설정해서, 서버가 적어도 그 간격만큼 자주 새 WAL 세그먼트 파일로 전환하도록 강제할 수 있어요. 강제 전환으로 일찍 아카이빙된 파일도 완전히 찬 파일과 같은 길이예요. 그래서 매우 짧은 archive_timeout을 설정하는 건 지혜롭지 않아요 — 아카이브 저장소가 부풀어 오르거든요. 1분 정도의 archive_timeout 설정이 보통 합리적이에요.
또한 방금 끝난 트랜잭션을 가능한 빨리 아카이빙하려면 pg_switch_wal로 수동으로 세그먼트 전환을 강제할 수 있어요.
wal_level이 minimal일 때는 일부 SQL 명령이 WAL 로깅을 피하도록 최적화돼요. 그런 문장 실행 중에 아카이빙이나 스트리밍 복제가 켜져 있었다면, WAL이 아카이브 복구에 충분한 정보를 담지 못했을 거예요. (크래시 복구는 영향받지 않아요.) 그래서 wal_level은 서버 시작 시에만 바꿀 수 있어요. 하지만 archive_command와 archive_library는 설정 파일 리로드로 바꿀 수 있어요. 쉘로 아카이빙하면서 일시적으로 아카이빙을 멈추고 싶다면, archive_command를 빈 문자열('')로 설정하는 방법이 있어요. 그러면 동작하는 archive_command가 다시 확립될 때까지 WAL 파일이 pg_wal/에 쌓이게 돼요.
베이스 백업 만들기
베이스 백업을 수행하는 가장 쉬운 방법은 pg_basebackup 도구를 쓰는 거예요. 일반 파일로든 tar 아카이브로든 베이스 백업을 만들 수 있어요. pg_basebackup이 제공하는 것보다 더 많은 유연성이 필요하면 저수준 API로도 베이스 백업을 만들 수 있어요.
베이스 백업에 걸리는 시간을 걱정할 필요는 없어요. 다만 평소 full_page_writes를 꺼두고 서버를 운영한다면, 백업 모드 동안 full_page_writes가 사실상 강제로 켜지므로 백업이 도는 동안 성능 저하를 볼 수 있어요.
백업을 사용하려면 파일 시스템 백업 도중과 이후에 생성된 모든 WAL 세그먼트 파일을 보관해야 해요. 이를 돕기 위해 베이스 백업 과정은 백업 히스토리 파일을 만들어 WAL 아카이브 영역에 즉시 저장해요. 이 파일은 파일 시스템 백업에 필요한 첫 WAL 세그먼트 파일의 이름을 따라 이름 지어져요. 예를 들어 시작 WAL 파일이 0000000100001234000055CD라면 백업 히스토리 파일은 0000000100001234000055CD.007C9330.backup 같은 이름이 돼요. (파일 이름의 두 번째 부분은 WAL 파일 내의 정확한 위치를 나타내며 보통 무시해도 돼요.) 파일 시스템 백업과 백업 중 사용된 WAL 세그먼트 파일(백업 히스토리 파일에 지정된)을 안전하게 아카이빙하면, 그보다 수치상 작은 이름의 모든 아카이브 WAL 세그먼트는 파일 시스템 백업 복구에 더 필요 없으므로 삭제할 수 있어요. 다만 데이터를 절대적으로 복구할 수 있게 하려면 백업 세트를 몇 개 보관하는 걸 고려해야 해요.
백업 히스토리 파일은 그냥 작은 텍스트 파일이에요. pg_basebackup에 준 레이블 문자열과, 백업의 시작·종료 시각과 WAL 세그먼트를 담고 있어요. 레이블로 연관 덤프 파일을 식별했다면 아카이브 히스토리 파일만으로 어떤 덤프 파일을 복원할지 알 수 있어요.
마지막 베이스 백업까지 거슬러 올라가는 모든 아카이브 WAL 파일을 보관해야 하므로, 베이스 백업 사이의 간격은 보통 아카이브 WAL 파일에 지출하고 싶은 저장 공간 양에 따라 골라야 해요. 복구가 필요할 때 복구에 쓸 시간도 고려해야 해요. 시스템이 모든 WAL 세그먼트를 재생해야 하는데, 마지막 베이스 백업 이후 오래됐다면 꽤 걸릴 수 있거든요.
증분 백업 만들기
--incremental 옵션을 지정하면 pg_basebackup으로 증분 백업을 만들 수 있어요. --incremental의 인자로 같은 서버의 이전 백업에 대한 백업 매니페스트를 제공해야 해요. 결과 백업에서 관계가 아닌 파일은 전부 포함되지만, 일부 관계 파일은 더 작은 증분 파일로 대체될 수 있어요. 그 증분 파일은 이전 백업 이후 바뀐 블록과 현재 버전의 파일을 재구성할 충분한 메타데이터만 담고 있어요.
어떤 블록을 백업해야 하는지 알아내기 위해 서버는 WAL 요약(summaries)을 쓰는데, 그것은 데이터 디렉터리 안의 pg_wal/summaries 디렉터리에 저장돼요. 필요한 요약 파일이 없으면 증분 백업 시도는 실패해요. 이 디렉터리에 있는 요약은 이전 백업의 시작 LSN부터 현재 백업의 시작 LSN까지 모든 LSN을 덮어야 해요. 서버는 현재 백업의 시작 LSN을 정한 직후에 WAL 요약을 찾으므로, 필요한 요약 파일이 디스크에 즉시 나타나지 않을 수도 있지만 서버는 없어진 파일이 나타날 때까지 기다려요. 이는 WAL 요약 프로세스가 뒤처졌을 때도 도움이 돼요. 하지만 필요한 파일이 이미 제거됐거나 WAL 요약기가 충분히 빠르게 따라잡지 못하면 증분 백업은 실패해요.
증분 백업을 복원할 때는 증분 백업 자체뿐 아니라, 증분 백업에서 생략된 블록을 공급하는 데 필요한 모든 이전 백업이 있어야 해요. 전체 백업을 쓰기 위한 모든 요건이 증분 백업에도 적용돼요. 예를 들어 파일 시스템 백업 도중과 이후에 생성된 모든 WAL 세그먼트 파일과 관련 WAL 히스토리 파일이 여전히 필요해요. 그리고 recovery.signal(또는 standby.signal)을 만들고 복구를 수행해야 해요. 복원 시 이전 백업이 필요하고 pg_combinebackup을 써야 한다는 건 그 모든 것 위에 추가되는 요건이에요. PostgreSQL에는 어떤 백업이 이후 증분 백업 복원의 기반으로 여전히 필요한지 알아내는 내장 메커니즘이 없다는 점을 명심하세요. 전체 백업과 증분 백업 사이의 관계를 직접 추적하고, 이후 증분 백업 복원에 필요할 수 있으므로 이전 백업을 제거하지 않도록 확실히 해야 해요.
증분 백업은 보통 데이터의 상당 부분이 바뀌지 않거나 천천히 바뀌는 비교적 큰 데이터베이스에서만 의미가 있어요. 작은 데이터베이스라면 증분 백업의 존재를 무시하고, 관리가 더 간단한 전체 백업만 뜨는 편이 단순해요. 모두가 크게 수정되는 큰 데이터베이스에서는 증분 백업이 전체 백업보다 그리 작지 않을 거예요.
증분 백업은 재생이 의존하는 이전 백업보다 나중의 체크포인트에서 시작해야만 가능해요. 프라이머리에서 증분 백업을 뜨면 각 백업이 새 체크포인트를 트리거하므로 이 조건은 항상 만족돼요. 스탠바이에서는 재생이 가장 최근 restartpoint부터 시작돼요. 그래서 이전 백업 이후 활동이 거의 없으면 새 restartpoint가 만들어지지 않아서, 스탠바이 서버의 증분 백업은 실패할 수 있어요.
저수준 API로 베이스 백업 만들기
pg_basebackup 대신 저수준 API로 베이스 백업을 만들 수도 있어요. 이 절차는 pg_basebackup보다 단계가 몇 개 더 많지만 비교적 단순해요. 이 단계들을 순서대로 실행하고, 다음 단계로 넘어가기 전에 각 단계의 성공을 확인하는 것이 아주 중요해요.
여러 백업은 동시에 실행될 수 있어요(이 백업 API로 시작한 것과 pg_basebackup으로 시작한 것 모두요).
- WAL 아카이빙이 켜져 있고 동작하는지 확인한다.
pg_backup_start를 실행할 권한이 있는 사용자로 서버에 접속하고(어느 데이터베이스든 상관없어요) 이 명령을 실행한다:
SELECT pg_backup_start(label => 'label', fast => false);
label은 이 백업 작업을 고유하게 식별하고 싶은 아무 문자열이에요. pg_backup_start를 호출한 접속은 백업이 끝날 때까지 유지해야 해요. 아니면 백업이 자동으로 중단돼요.
온라인 백업은 항상 체크포인트의 시작에 시작돼요. 기본적으로 pg_backup_start는 다음 정기 예정 체크포인트가 완료되길 기다려요(checkpoint_timeout과 checkpoint_completion_target 설정 매개변수 참고). 이것은 실행 중인 시스템에 미치는 영향을 최소화하므로 보통 선호돼요. 가능한 한 빨리 백업을 시작하고 싶다면 pg_backup_start의 두 번째 매개변수로 true를 넘기면, 가능한 한 많은 I/O를 써서 가능한 한 빨리 끝나는 즉시 체크포인트를 요청해요.
-
tar나cpio같은 편리한 파일 시스템 백업 도구로 백업을 수행해요(pg_dump나pg_dumpall이 아니에요). 이 작업 동안 데이터베이스의 정상 운영을 멈출 필요도, 그렇게 하는 게 바람직하지도 않아요. -
앞서와 같은 접속에서 이 명령을 실행한다:
SELECT * FROM pg_backup_stop(wait_for_archive => true);
이것은 백업 모드를 종료해요. 프라이머리에서는 다음 WAL 세그먼트로의 자동 전환도 수행해요. 스탠바이에서는 WAL 세그먼트를 자동 전환할 수 없으므로, 프라이머리에서 pg_switch_wal을 실행해 수동 전환할 수도 있어요. 전환 이유는 백업 구간 동안 마지막으로 쓰인 WAL 세그먼트 파일이 아카이빙될 준비가 되도록 하려는 거예요.
pg_backup_stop은 세 값이 담긴 행 하나를 반환해요. 그 두 번째 필드는 백업 루트 디렉터리의 backup_label 파일에 써야 해요. 세 번째 필드는 비어 있지 않으면 tablespace_map 파일에 써야 해요. 이 파일들은 백업이 동작하는 데 필수적이며, 수정 없이 바이트 단위로 그대로 써야 해요. 이진 모드로 파일을 열어야 할 수도 있어요.
- 백업 중 활성이던 WAL 세그먼트 파일이 아카이빙되면 끝나요.
pg_backup_stop의 첫 번째 반환 값이 식별하는 파일이 완전한 백업 파일 세트를 형성하는 데 필요한 마지막 세그먼트예요. 프라이머리에서archive_mode가 켜져 있고wait_for_archive매개변수가true면,pg_backup_stop은 마지막 세그먼트가 아카이빙될 때까지 반환하지 않아요. 스탠바이에서는pg_backup_stop이 기다리려면archive_mode가always여야 해요. 이 파일들의 아카이빙은 이미archive_command나archive_library를 설정했으므로 자동으로 일어나요. 대부분 빠르게 일어나지만 지연이 없는지 아카이브 시스템을 모니터링하는 게 좋아요. 아카이브 명령·라이브러리의 실패로 아카이브 프로세스가 뒤처졌다면, 아카이브가 성공하고 백업이 완료될 때까지 계속 재시도해요.pg_backup_stop의 실행에 시간 제한을 두고 싶다면 적절한statement_timeout값을 설정하세요. 다만 이로 인해pg_backup_stop이 종료되면 백업이 유효하지 않을 수 있다는 점을 유의하세요.
백업 프로세스가 백업에 필요한 모든 WAL 세그먼트 파일이 성공적으로 아카이빙됐음을 모니터링하고 보장한다면, wait_for_archive 매개변수(기본값 true)를 false로 설정해 stop 백업 레코드가 WAL에 쓰이자마자 pg_backup_stop이 반환하게 할 수 있어요. 기본적으로 pg_backup_stop은 모든 WAL이 아카이빙될 때까지 기다리는데, 시간이 걸릴 수 있어요. 이 옵션은 주의해서 써야 해요. WAL 아카이빙을 올바르게 모니터링하지 않으면 백업이 모든 WAL 파일을 포함하지 못해 불완전해지고 복원할 수 없게 될 수 있거든요.
데이터 디렉터리 백업 시 주의점
일부 파일 시스템 백업 도구는 복사하려는 파일이 복사 도중 바뀌면 경고나 오류를 내요. 활성 데이터베이스의 베이스 백업을 뜰 때 이런 상황은 정상이고 오류가 아니에요. 하지만 이런 종류의 불평을 실제 오류와 구분할 수 있게 해야 해요. 예를 들어 일부 rsync 버전은 "사라진 원본 파일(vanished source files)"에 대해 별도의 종료 코드를 반환하고, 이 종료 코드를 비오류로 받아들이는 드라이버 스크립트를 쓸 수 있어요. 또 일부 GNU tar 버전은 파일이 복사 도중 잘리면 치명적 오류와 구분할 수 없는 오류 코드를 반환해요. 다행히 GNU tar 1.16 이상은 백업 중 파일이 바뀌면 1로, 다른 오류는 2로 종료해요. GNU tar 1.23 이상에서는 --warning=no-file-changed --warning=no-file-removed 경고 옵션으로 관련 경고 메시지를 숨길 수 있어요.
백업에 데이터베이스 클러스터 디렉터리(예: /usr/local/pgsql/data) 아래의 모든 파일이 포함되게 확실히 하세요. 이 디렉터리 아래에 있지 않은 테이블스페이스를 쓴다면 그것들도 포함하도록 조심하세요(그리고 백업이 심볼릭 링크를 링크로 아카이빙하게 해야 해요. 그렇지 않으면 복원 시 테이블스페이스가 망가져요).
하지만 클러스터의 pg_wal/ 하위 디렉터리 안의 파일은 백업에서 제외해야 해요. 이 약간의 조정은 복원할 때 실수 위험을 줄여주므로 가치가 있어요. pg_wal/이 클러스터 디렉터리 밖을 가리키는 심볼릭 링크라면 쉽게 정리할 수 있는데, 성능상 이유로 흔한 구성이에요. 실행 중인 postmaster에 대한 정보를 기록하는 postmaster.pid와 postmaster.opts도 제외하고 싶을 거예요. 결국 이 백업을 쓸 postmaster에 관한 것이 아니거든요. (이 파일들은 pg_ctl을 헷갈리게 할 수 있어요.)
클러스터의 pg_replslot/ 디렉터리 안의 파일도 백업에서 제외하는 게 좋은 경우가 많아요. 그래야 프라이머리에 존재하는 복제 슬롯이 백업의 일부가 되지 않아요. 그렇지 않으면 백업으로 스탠바이를 만들 때 스탠바이에서 WAL 파일이 무한정 보존되고, 핫 스탠바이 피드백이 켜져 있으면 프라이머리에서 블로트가 생길 수 있어요. 그 복제 슬롯을 쓰는 클라이언트들이 스탠바이가 아니라 프라이머리에 계속 접속해 슬롯을 갱신하거든요. 백업이 새 프라이머리를 만드는 데만 쓰일 의도라 해도, 복제 슬롯을 복사하는 건 특히 유용하지 않을 거예요. 새 프라이머리가 온라인이 될 때쯤이면 그 슬롯 내용이 크게 뒤떨어져 있을 가능성이 크거든요.
pg_dynshmem/, pg_notify/, pg_serial/, pg_snapshots/, pg_stat_tmp/, pg_subtrans/ 디렉터리의 내용(디렉터리 자체는 아님)은 postmaster 시작 시 초기화되므로 백업에서 제외할 수 있어요.
pgsql_tmp로 시작하는 모든 파일·디렉터리는 백업에서 제외할 수 있어요. 이 파일들은 postmaster 시작 시 제거되고 필요한 경우 디렉터리는 다시 만들어진답니다.
해당 이름의 파일이 발견될 때마다 pg_internal.init 파일은 백업에서 제외할 수 있어요. 이 파일은 복구 시 항상 다시 빌드되는 관계 캐시 데이터를 담고 있어요.
백업 레이블 파일은 pg_backup_start에 준 레이블 문자열, pg_backup_start가 실행된 시각, 시작 WAL 파일의 이름을 포함해요. 혼동이 있으면 백업 파일 안을 들여다보고 덤프 파일이 정확히 어느 백업 세션에서 왔는지 판별할 수 있어요. 테이블스페이스 맵 파일은 pg_tblspc/ 디렉터리에 존재하는 심볼릭 링크 이름과 각 심볼릭 링크의 전체 경로를 포함해요. 이 파일들은 단지 참고용이 아니에요. 그 존재와 내용은 시스템 복구 과정이 제대로 동작하는 데 중요해요.
서버가 정지된 상태에서도 백업을 만들 수 있어요. 이 경우 pg_backup_start나 pg_backup_stop을 쓸 수 없으므로, 어떤 백업이 어떤 것이고 연관 WAL 파일이 얼마나 거슬러 올라가는지 직접 추적해야 해요. 일반적으로 위의 연속 아카이빙 절차를 따르는 게 더 좋아요.
연속 아카이브 백업으로 복구하기
최악의 상황이 벌어져 백업에서 복구해야 할 때의 절차예요.
- 서버가 실행 중이면 멈춘다.
- 공간이 된다면 클러스터 데이터 디렉터리 전체와 모든 테이블스페이스를 나중에 필요할 수 있으니 임시 위치로 복사한다. 주의할 점: 이 예방 조치는 기존 데이터베이스의 사본 두 벌을 담을 충분한 여유 공간이 필요해요. 공간이 부족하면 최소한 클러스터의
pg_wal하위 디렉터리 내용을 저장해두어야 해요. 시스템이 다운되기 전에 아카이빙되지 않은 WAL 파일이 있을 수 있거든요. - 클러스터 데이터 디렉터리 아래와, 사용 중인 모든 테이블스페이스 루트 디렉터리 아래의 모든 기존 파일과 하위 디렉터리를 제거한다.
- 전체 백업을 복원한다면 데이터베이스 파일을 대상 디렉터리에 직접 복원하면 돼요. 올바른 소유권(데이터베이스 시스템 사용자 — root가 아니라요!)과 올바른 권한으로 복원되게 확실히 하세요. 테이블스페이스를 쓴다면
pg_tblspc/의 심볼릭 링크가 올바르게 복원됐는지 확인해야 해요. - 증분 백업을 복원한다면, 증분 백업과 그것이 직간접적으로 의존하는 모든 이전 백업을 복원을 수행하는 머신에 복원해야 해요. 이 백업들은 별도의 디렉터리에 둬야 해요. 실행 중인 서버가 최종적으로 위치해야 할 대상 디렉터리에 두면 안 되죠. 그 다음
pg_combinebackup으로 전체 백업과 이후의 모든 증분 백업에서 데이터를 가져와 대상 디렉터리에 '합성 전체 백업'을 써내요. 위와 같이 권한과 테이블스페이스 링크가 올바른지 확인한다. pg_wal/에 있는 파일을 제거한다. 이건 파일 시스템 백업에서 온 것이므로 아마 최신이 아니라 쓸모없을 거예요.pg_wal/을 아예 아카이빙하지 않았다면 올바른 권한으로 다시 만들고, 예전에 심볼릭 링크로 설정했었다면 심볼릭 링크로 다시 구축되도록 조심한다.- 2단계에서 저장해 둔 아카이빙되지 않은 WAL 세그먼트 파일이 있다면
pg_wal/에 복사한다. (복사가 좋고 이동이 아니에요. 문제가 생겨 다시 시작해야 할 때 수정되지 않은 파일을 여전히 갖도록 말이죠.) postgresql.conf에 복구 구성 설정을 하고(공식문서 Section 19.5.5 참고) 클러스터 데이터 디렉터리에recovery.signal파일을 만든다. 복구가 성공했는지 확신할 때까지 일반 사용자 접속을 막도록pg_hba.conf를 일시적으로 수정하고 싶을 수도 있어요.- 서버를 시작한다. 서버는 복구 모드로 들어가 필요한 아카이브 WAL 파일을 순서대로 읽는다. 외부 오류로 복구가 중단되면 서버를 그냥 재시작하면 되고 복구를 계속해요. 복구 과정이 완료되면 서버는
recovery.signal을 제거하고(나중에 실수로 복구 모드에 다시 들어가는 걸 막으려고) 정상 데이터베이스 운영을 시작한다. - 데이터베이스 내용을 검사해 원하는 상태로 복구됐는지 확인한다. 아니면 1단계로 돌아간다. 괜찮다면
pg_hba.conf를 정상으로 복원해 사용자가 접속하게 한다.
이 모든 것의 핵심은 어떻게 복구하고 얼마나 멀리 복구를 실행할지 설명하는 복구 구성을 세우는 거예요. 반드시 지정해야 할 한 가지는 restore_command로, PostgreSQL에게 아카이브 WAL 파일 세그먼트를 어떻게 가져올지 알려줘요. archive_command처럼 이것도 쉘 명령 문자열이에요. %f는 원하는 WAL 파일의 이름으로, %p는 WAL 파일을 복사할 경로명으로 치환될 수 있어요. (경로명은 현재 작업 디렉터리, 즉 클러스터의 데이터 디렉터리에 상대적이에요.) 명령에 실제 % 문자를 넣으려면 %%를 써요. 가장 단순한 유용한 명령은 이래요.
restore_command = 'cp /mnt/server/archivedir/%f %p'
이 명령은 이전에 아카이빙된 WAL 세그먼트를 /mnt/server/archivedir 디렉터리에서 복사해요. 물론 훨씬 더 복잡한 걸 쓸 수도 있는데, 운영자에게 적절한 테이프를 마운트하라고 요청하는 쉘 스크립트일 수도 있어요.
명령이 실패 시 0이 아닌 종료 상태를 반환하는 것이 중요해요. 명령은 아카이브에 없는 파일을 요청받는 경우가 있는데, 그렇게 요청받으면 0이 아닌 값을 반환해야 해요. 그건 오류 조건이 아니에요. 예외: 명령이 (데이터베이스 서버 종료의 일부로 쓰이는 SIGTERM 외의) 신호로 종료되거나 쉘이 오류(예: command not found)를 내면 복구는 중단되고 서버는 시작되지 않아요.
요청되는 파일이 전부 WAL 세그먼트 파일은 아니에요. .history 접미사를 가진 파일 요청도 예상해야 해요. 그리고 %p 경로의 기본 이름(base name)은 %f와 다르다는 점도 유의하세요. 서로 바꿔 쓸 수 있다고 기대하지 마세요.
아카이브에서 찾을 수 없는 WAL 세그먼트는 pg_wal/에서 찾아져요. 그래서 최근의 미아카이빙 세그먼트도 쓸 수 있어요. 다만 아카이브에서 사용 가능한 세그먼트가 pg_wal/의 파일보다 우선해서 사용돼요.
보통 복구는 사용 가능한 모든 WAL 세그먼트를 진행해서 데이터베이스를 현재 시점(또는 사용 가능한 WAL 세그먼트로 가능한 한 가깝게)으로 복원해요. 그래서 정상 복구는 '파일을 찾을 수 없음(file not found)' 메시지로 끝나요. 정확한 오류 메시지 텍스트는 restore_command 선택에 따라 달라져요. 또 00000001.history 같은 파일에 대해 복구 시작 시 오류 메시지를 볼 수도 있어요. 이것도 정상이고 단순 복구 상황에서 문제를 나타내지 않아요.
이전 시점으로 복구하고 싶다면(예: 주니어 DBA가 메인 트랜잭션 테이블을 떨어뜨리기 직전으로), 필요한 중지 지점을 지정하면 돼요. '복구 대상(recovery target)'이라고 불리는 이 중지 지점은 날짜/시간, 이름 있는 복원 지점(named restore point), 특정 트랜잭션 ID 완료 중 하나로 지정할 수 있어요. 이 글을 쓰는 시점에는 정확히 어떤 트랜잭션 ID를 쓸지 식별하는 도구가 없어서 날짜/시간과 이름 있는 복원 지점 옵션만 실용적이에요.
참고: 중지 지점은 베이스 백업의 종료 시각, 즉
pg_backup_stop의 종료 시각 이후여야 해요. 백업이 진행되던 시점으로 복구하는 데는 베이스 백업을 쓸 수 없어요. (그런 시점으로 복구하려면 이전 베이스 백업으로 돌아가 그쪽에서 앞으로 진행해야 해요.)
복구가 손상된 WAL 데이터를 찾으면 그 지점에서 복구가 멈추고 서버는 시작되지 않아요. 그런 경우 손상 지점보다 앞의 '복구 대상'을 지정해 처음부터 복구 프로세스를 다시 실행해 정상적으로 완료되게 할 수 있어요. 외부 이유(시스템 크래시나 WAL 아카이브 접근 불가 등)로 복구가 실패하면 복구를 그냥 재시작하면 되고, 실패한 지점에서 거의 이어져 재시작돼요. 복구 재시작은 정상 운영의 체크포인트와 비슷해요. 서버는 주기적으로 모든 상태를 디스크로 강제하고, 이미 처리된 WAL 데이터를 다시 스캔할 필요가 없도록 pg_control 파일을 갱신해요.
타임라인 (Timelines)
데이터베이스를 이전 시점으로 복원하는 능력은 시간 여행과 평행 우주에 관한 과학 소설 같은 복잡함을 만들어요. 예를 들어 원래 데이터베이스 역사에서 화요일 오후 5:15에 중요한 테이블을 떨어뜨렸는데, 수요일 정오가 되어서야 실수를 알아차렸다고 해볼게요. 당황하지 않고 백업을 꺼내 화요일 오후 5:14 시점으로 복원해 다시 운영할 수 있어요. 이 데이터베이스 우주의 역사에서는 테이블을 떨어뜨린 적이 없는 거예요. 하지만 나중에 이게 좋은 생각이 아니었다는 걸 깨닫고, 원래 역사의 수요일 아침 어느 시점으로 돌아가고 싶어질 수도 있어요. 그런데 데이터베이스가 운영되는 동안 돌아가고 싶은 그 시점까지 이어지는 WAL 세그먼트 파일 중 일부를 덮어써 버렸다면 그럴 수 없어요. 그래서 이를 피하려면 특정 시점 복구 이후 생성된 WAL 레코드 시퀀스를 원래 데이터베이스 역사에서 생성된 것과 구분해야 해요.
이 문제를 다루기 위해 PostgreSQL에는 타임라인(timeline) 개념이 있어요. 아카이브 복구가 완료될 때마다 새 타임라인이 생성되어 그 복구 이후 생성된 WAL 레코드 시퀀스를 식별해요. 타임라인 ID 번호는 WAL 세그먼트 파일 이름의 일부이므로, 새 타임라인은 이전 타임라인들이 생성한 WAL 데이터를 덮어쓰지 않아요. 예를 들어 WAL 파일 이름 0000000100001234000055CD에서 앞의 00000001은 16진수 타임라인 ID예요. (다른 맥락, 예: 서버 로그 메시지에서는 타임라인 ID가 보통 10진수로 출력돼요.)
실제로 여러 다른 타임라인을 아카이빙하는 것이 가능해요. 쓸모없어 보일 수 있지만 종종 인생을 구해줘요. 어떤 시점으로 복구해야 할지 정확히 확신하지 못해서, 옛 역사에서 분기할 최적의 지점을 찾을 때까지 시행착오로 몇 번 특정 시점 복구를 해야 하는 상황을 생각해보세요. 타임라인이 없다면 이 과정은 곧 관리 불가능한 난장판을 만들 거예요. 타임라인이 있으면 이전에 포기했던 타임라인 분기를 포함해, 이전 상태 어느 것이든 복구할 수 있어요.
새 타임라인이 생성될 때마다 PostgreSQL은 어느 타임라인에서 언제 분기했는지 보여주는 '타임라인 히스토리' 파일을 만들어요. 이 히스토리 파일은 여러 타임라인을 담은 아카이브에서 복구할 때 시스템이 올바른 WAL 세그먼트 파일을 고르는 데 필요해요. 그래서 WAL 세그먼트 파일처럼 WAL 아카이브 영역에 아카이빙돼요. 히스토리 파일은 그냥 작은 텍스트 파일이라서, (큰 세그먼트 파일과 달리) 무기한 보관하기 싸고 적절해요. 원하면 히스토리 파일에 코멘트를 추가해 이 특정 타임라인이 어떻게·왜 만들어졌는지 자신의 메모를 기록할 수 있어요. 실험 결과 여러 다른 타임라인들이 뒤엉켜 있을 때 이런 코멘트가 특히 값져요.
복구의 기본 동작은 아카이브에서 찾은 최신 타임라인으로 복구하는 거예요. 베이스 백업이 수행될 당시 유효하던 타임라인으로, 또는 특정 자식 타임라인으로 복구하고 싶다면(즉 복구 시도 이후에 생성된 어떤 상태로 돌아가고 싶다면), recovery_target_timeline에 current나 대상 타임라인 ID를 지정해야 해요. 베이스 백업보다 일찍 분기한 타임라인으로는 복구할 수 없어요.
팁과 예시
독립형 핫 백업 (Standalone Hot Backups)
PostgreSQL의 백업 기능으로 독립형 핫 백업을 만들 수 있어요. 이런 백업은 특정 시점 복구에는 쓸 수 없지만, 보통 pg_dump 덤프보다 백업·복원이 훨씬 빨라요. (그 대신 pg_dump 덤프보다 훨씬 커서, 어떤 경우에는 속도 이점이 상쇄될 수 있어요.) 베이스 백업처럼 독립형 핫 백업을 만드는 가장 쉬운 방법은 pg_basebackup 도구를 쓰는 거예요. 호출할 때 -X 매개변수를 포함하면 백업을 쓰는 데 필요한 모든 WAL이 백업에 자동으로 포함되고, 백업을 복원하는 데 특별한 조치가 필요 없어요.
압축 아카이브 로그
아카이브 저장 공간이 걱정된다면 gzip으로 아카이브 파일을 압축할 수 있어요.
archive_command = 'gzip < %p > /mnt/server/archivedir/%f.gz'
복구할 때는 gunzip을 써야 해요.
restore_command = 'gunzip < /mnt/server/archivedir/%f.gz > %p'
archive_command 스크립트
많은 사람들이 archive_command를 정의하는 데 스크립트를 쓰기로 해서 postgresql.conf 항목이 매우 단순해 보이게 해요.
archive_command = 'local_backup_script.sh "%p" "%f"'
아카이빙 과정에서 단일 명령 이상을 쓰고 싶을 때마다 별도 스크립트 파일을 쓰는 게 좋아요. 그러면 모든 복잡성을 bash나 perl 같은 대중적인 스크립트 언어로 작성된 스크립트 안에서 관리할 수 있어요.
스크립트 안에서 해결할 수 있는 요구사항의 예:
- 안전한 오프사이트 데이터 저장소로 데이터 복사
- WAL 파일을 한 번에 하나씩이 아니라 3시간마다 전송하도록 배칭
- 다른 백업·복구 소프트웨어와 연동
- 오류를 보고하도록 모니터링 소프트웨어와 연동
팁:
archive_command스크립트를 쓸 때는logging_collector를 켜두는 게 좋아요. 그러면 스크립트가 stderr로 쓰는 모든 메시지가 데이터베이스 서버 로그에 나타나서, 복잡한 구성이 실패해도 쉽게 진단할 수 있어요.
연속 아카이빙의 한계
이 글을 쓰는 시점에 연속 아카이빙 기법에는 몇 가지 한계가 있고, 아마 향후 릴리스에서 고쳐질 거예요.
- 베이스 백업을 뜨는 동안
CREATE DATABASE명령이 실행되고, 그CREATE DATABASE가 복사한 템플릿 데이터베이스가 베이스 백업이 진행 중일 때 수정되면, 복구가 그 수정 사항을 생성된 데이터베이스에도 전파할 수 있어요. 물론 바람직하지 않죠. 이 위험을 피하려면 베이스 백업 중엔 템플릿 데이터베이스를 수정하지 않는 게 가장 좋아요. CREATE TABLESPACE명령은 리터럴 절대 경로로 WAL에 기록되므로, 같은 절대 경로의 테이블스페이스 생성으로 재생돼요. WAL이 다른 머신에서 재생되면 바람직하지 않을 수 있어요. 같은 머신에서 새 데이터 디렉터리로 재생되더라도 위험할 수 있어요. 재생이 원래 테이블스페이스 내용을 여전히 덮어쓰거든요. 이런 문제를 피하려면 테이블스페이스를 만들거나 지운 뒤 새 베이스 백업을 뜨는 게 모범 사례예요.- 기본 WAL 형식은 많은 디스크 페이지 스냅샷을 포함해서 꽤 부피가 커요. 이 페이지 스냅샷은 부분적으로 쓰인 디스크 페이지를 고쳐야 할 수도 있으므로 크래시 복구를 지원하려고 설계된 거예요. 시스템 하드웨어와 소프트웨어에 따라 부분 쓰기 위험이 무시할 만큼 작을 수 있고, 그 경우
full_page_writes매개변수로 페이지 스냅샷을 꺼서 아카이브 WAL 파일의 총 부피를 크게 줄일 수 있어요. (그렇게 하기 전에 공식문서 Chapter 28의 참고와 경고를 읽어보세요.) 페이지 스냅샷을 꺼도 WAL을 PITR 연산에 쓰는 것을 막지 않아요.full_page_writes가 켜져 있어도 불필요한 페이지 사본을 제거해 아카이브 WAL 데이터를 압축하는 것은 향후 개발 영역이에요. 그동안 관리자는 체크포인트 간격 매개변수를 가능한 한 늘려 WAL에 포함되는 페이지 스냅샷 수를 줄이고 싶어할 거예요.