백업과 복원 (Backup and Restore)
백업과 복원 (Backup and Restore)
가치 있는 데이터를 담은 모든 것에 그렇듯, PostgreSQL 데이터베이스도 정기적으로 백업해야 해요. 절차 자체는 기본적으로 단순하지만, 그 밑에 깔린 기술과 전제 조건을 제대로 이해하는 게 중요해요. 이 글에서는 백업을 세 가지 방식으로 나눠서 설명할게요.
PostgreSQL 데이터를 백업하는 방법은 크게 세 가지로 나뉘어요.
- SQL 덤프 (SQL dump)
- 파일 시스템 레벨 백업 (File system level backup)
- 연속 아카이빙 (Continuous archiving)
각각 장단점이 다르고, 다음 절에서 하나씩 차례로 다뤄요.
25.1. SQL 덤프 (SQL Dump)
이 덤프 방식의 핵심 아이디어는, 서버에 다시 넣으면 덤프를 받은 시점의 데이터베이스 상태를 그대로 재현할 수 있는 SQL 명령 파일을 만드는 거예요. PostgreSQL은 이 목적을 위한 유틸리티 프로그램 pg_dump를 제공해요. 기본 사용법은 이렇게 생겼어요.
pg_dump dbname > dumpfile
보시다시피 pg_dump는 결과를 표준 출력(standard output)으로 써요. 이게 왜 유용한지는 잠시 뒤에 볼게요. 위 명령이 텍스트 파일을 만들지만, pg_dump는 병렬 처리와 객체 복원에 대한 더 세밀한 제어가 가능한 다른 형식의 파일도 만들 수 있어요.
pg_dump는 일반 PostgreSQL 클라이언트 애플리케이션이에요 (특별히 영리하긴 하지만요). 즉 데이터베이스에 접근할 수 있는 어떤 원격 호스트에서도 이 백업 절차를 수행할 수 있다는 뜻이에요. 다만 pg_dump는 특별한 권한으로 동작하지 않는다는 걸 기억하세요. 특히 백업하려는 모든 테이블에 대해 읽기 권한이 있어야 해요. 그래서 데이터베이스 전체를 백업하려면 거의 항상 데이터베이스 슈퍼유저로 실행해야 해요. (전체 데이터베이스 백업에 필요한 권한이 없다면, -n schema나 -t table 같은 옵션으로 접근 권한이 있는 일부만 백업할 수도 있어요.)
pg_dump가 접속할 데이터베이스 서버를 지정하려면 명령행 옵션 -h host와 -p port를 사용해요. 기본 호스트는 로컬 호스트 또는 PGHOST 환경 변수가 지정하는 값이에요. 마찬가지로 기본 포트는 PGPORT 환경 변수, 그게 없으면 컴파일 시 내장된 기본값이에요. (편리하게도 서버도 보통 같은 내장 기본값을 가져요.)
다른 PostgreSQL 클라이언트 애플리케이션처럼 pg_dump는 기본적으로 현재 운영체제 사용자 이름과 같은 데이터베이스 사용자 이름으로 접속해요. -U 옵션을 지정하거나 PGUSER 환경 변수를 설정하면 이걸 바꿀 수 있어요. pg_dump 접속도 일반적인 클라이언트 인증 메커니즘(제20장에서 설명)을 따른다는 걸 기억하세요.
pg_dump가 아래에서 설명할 다른 백업 방법보다 가진 중요한 장점은, 그 출력물을 일반적으로 더 최신 버전의 PostgreSQL에 다시 로드할 수 있다는 점이에요. 반면 파일 레벨 백업과 연속 아카이빙은 둘 다 서버 버전에 아주 민감해요. pg_dump는 또한 32비트에서 64비트 서버로 옮기는 것처럼 다른 머신 아키텍처로 데이터베이스를 전송할 때 동작하는 유일한 방법이에요.
pg_dump가 만든 덤프는 내부적으로 일관적이에요. 즉 그 덤프는 pg_dump가 실행을 시작한 시점의 데이터베이스 스냅샷을 나타내요. pg_dump는 작업하는 동안 데이터베이스의 다른 작업을 막지 않아요. (예외는 배타적 잠금이 필요한 작업인데, 대부분의 ALTER TABLE 형태가 그렇죠.)
25.1.1. 덤프 복원하기 (Restoring the Dump)
pg_dump가 만든 텍스트 파일은 psql 프로그램이 기본 설정으로 읽도록 만들어져 있어요. 텍스트 덤프를 복원하는 일반적인 명령 형태는 이래요.
psql -X dbname < dumpfile
여기서 dumpfile은 pg_dump 명령이 출력한 파일이고, dbname 데이터베이스는 이 명령이 만들어 주지 않아요. 그래서 psql을 실행하기 전에 template0에서 직접 만들어야 해요 (예: createdb -T template0 dbname). psql이 기본 설정으로 실행되도록 하려면 -X(--no-psqlrc) 옵션을 사용해요. psql은 접속할 서버와 사용자 이름을 지정하는 pg_dump와 비슷한 옵션을 지원해요. 자세한 내용은 psql 참조 페이지를 보세요.
텍스트가 아닌 파일 덤프는 pg_restore 유틸리티로 복원해야 해요.
SQL 덤프를 복원하기 전에, 덤프된 데이터베이스에서 객체를 소유했거나 객체에 권한을 부여받은 모든 사용자가 이미 존재해야 해요. 없으면 복원이 원래 소유권이나 권한으로 객체를 재현하지 못해요. (때로는 이게 바라는 결과일 수도 있지만, 보통은 아니에요.)
기본적으로 psql 스크립트는 SQL 오류를 만나도 계속 실행돼요. 이 동작을 바꾸려면 psql을 ON_ERROR_STOP 변수를 설정해 실행하면, SQL 오류가 발생했을 때 psql이 종료 상태 3으로 빠져나와요.
psql -X --set ON_ERROR_STOP=on dbname < dumpfile
어느 쪽이든 결국 부분적으로만 복원된 데이터베이스를 갖게 돼요. 대신 전체 덤프를 하나의 트랜잭션으로 복원하도록 지정할 수도 있어요. 그러면 복원이 완전히 완료되거나 완전히 롤백되죠. 이 모드는 psql에 -1 또는 --single-transaction 명령행 옵션을 넘겨 지정할 수 있어요. 이 모드를 쓸 때는 사소한 오류 하나가 이미 몇 시간 동안 돌아간 복원을 롤백할 수 있다는 점을 알아두세요. 그래도 부분 복원 후 복잡한 데이터베이스를 수동으로 정리하는 것보다 나을 수 있어요.
pg_dump와 psql이 파이프로 쓰고 읽을 수 있다는 점 덕분에, 데이터베이스를 한 서버에서 다른 서버로 직접 덤프할 수 있어요. 예를 들면 이렇게요.
pg_dump -h host1 dbname | psql -X -h host2 dbname
중요: pg_dump가 만든 덤프는
template0을 기준으로 해요. 즉template1을 통해 추가한 언어나 프로시저 등도 pg_dump가 덤프한다는 뜻이에요. 그래서 복원할 때 맞춤형template1을 사용 중이라면 위 예처럼 빈 데이터베이스를template0에서 만들어야 해요.
백업 복원 후에는 각 데이터베이스에서 ANALYZE를 실행해서 쿼리 최적화 프로그램이 유용한 통계를 갖도록 하는 게 좋아요. 자세한 내용은 24.1.3절과 24.1.6절을, 많은 데이터를 PostgreSQL에 효율적으로 넣는 방법은 14.4절을 참고하세요.
25.1.2. pg_dumpall 사용하기 (Using pg_dumpall)
pg_dump는 한 번에 하나의 데이터베이스만 덤프하고, 역할(role)이나 테이블스페이스 정보는 덤프하지 않아요. 그것들은 데이터베이스별이 아니라 클러스터 전체 단위라서 그래요. 데이터베이스 클러스터 전체 내용을 편리하게 덤프하도록 pg_dumpall 프로그램이 제공돼요. pg_dumpall은 주어진 클러스터의 각 데이터베이스를 백업하고, 역할과 테이블스페이스 정의 같은 클러스터 전체 데이터도 보존해요. 기본 사용법은 이래요.
pg_dumpall > dumpfile
결과 덤프는 psql로 복원할 수 있어요.
psql -X -f dumpfile postgres
(실제로는 아무 기존 데이터베이스 이름이나 시작점으로 지정할 수 있지만, 빈 클러스터에 로드하는 경우라면 보통 postgres를 사용해야 해요.) pg_dumpall 덤프를 복원할 때는 데이터베이스 슈퍼유저 접근이 항상 필요해요. 역할과 테이블스페이스 정보를 복원하려면 그게 필요하거든요. 테이블스페이스를 사용한다면 덤프 안의 테이블스페이스 경로가 새 설치 상황에 맞는지 확인하세요.
pg_dumpall은 역할, 테이블스페이스, 빈 데이터베이스를 재생성하는 명령을 내보낸 다음 각 데이터베이스에 대해 pg_dump를 호출하는 방식으로 동작해요. 즉 각 데이터베이스는 내부적으로 일관되지만, 서로 다른 데이터베이스의 스냅샷은 동기화되지 않아요.
클러스터 전체 데이터만 단독으로 덤프하려면 pg_dumpall의 --globals-only 옵션을 사용해요. 개별 데이터베이스마다 pg_dump 명령을 실행해서 클러스터를 백업하려면 이 옵션을 사용하는 게 필요해요.
25.1.3. 대용량 데이터베이스 다루기 (Handling Large Databases)
일부 운영체제는 파일 크기 최대치 제한이 있어서 큰 pg_dump 출력 파일을 만들 때 문제가 생겨요. 다행히 pg_dump는 표준 출력으로 쓸 수 있으니, 표준 Unix 도구를 사용해 이 잠재적 문제를 우회할 수 있어요. 몇 가지 방법이 있어요.
압축 덤프 사용하기. gzip 같은 좋아하는 압축 프로그램을 쓸 수 있어요.
pg_dump dbname | gzip > filename.gz
다음과 같이 다시 로드해요.
gunzip -c filename.gz | psql dbname
또는:
cat filename.gz | gunzip | psql dbname
split 사용하기. split 명령으로 출력을 기본 파일 시스템이 받아들일 만한 크기의 작은 파일들로 나눌 수 있어요. 예를 들어 2기가바이트 단위로 나누려면:
pg_dump dbname | split -b 2G - filename
다음과 같이 다시 로드해요.
cat filename* | psql dbname
GNU split을 쓴다면 split과 gzip을 함께 쓸 수 있어요.
pg_dump dbname | split -b 2G --filter='gzip > $FILE.gz'
zcat으로 복원할 수 있어요.
pg_dump의 맞춤 덤프 형식 사용하기. PostgreSQL이 zlib 압축 라이브러리가 설치된 시스템에서 빌드됐다면, 맞춤 덤프 형식은 출력 파일에 쓰면서 데이터를 압축해요. 이것은 gzip을 쓴 것과 비슷한 덤프 파일 크기를 만들어 주는데, 테이블을 선택적으로 복원할 수 있다는 부가 장점이 있어요. 다음 명령은 맞춤 덤프 형식으로 데이터베이스를 덤프해요.
pg_dump -Fc dbname > filename
맞춤 형식 덤프는 psql용 스크립트가 아니라 반드시 pg_restore로 복원해야 해요. 예를 들어:
pg_restore -d dbname filename
자세한 내용은 pg_dump와 pg_restore 참조 페이지를 보세요.
매우 큰 데이터베이스라면 split을 나머지 두 방법 중 하나와 결합해야 할 수도 있어요.
pg_dump의 병렬 덤프 기능 사용하기. 큰 데이터베이스 덤프를 빠르게 하려면 pg_dump의 병렬 모드를 쓸 수 있어요. 이 방식은 여러 테이블을 동시에 덤프해요. 병렬 수준은 -j 매개변수로 조절해요. 병렬 덤프는 "directory" 아카이브 형식에서만 지원돼요.
pg_dump -j num -F d -f out.dir dbname
pg_restore -j로 덤프를 병렬로 복원할 수 있어요. 이것은 pg_dump -j로 만들었는지와 관계없이 "custom" 또는 "directory" 아카이브 형식 모두에 동작해요.
25.2. 파일 시스템 레벨 백업 (File System Level Backup)
대안적인 백업 전략은 PostgreSQL이 데이터베이스 데이터를 저장하는 데 쓰는 파일을 직접 복사하는 거예요. 이 파일들이 어디 있는지는 18.2절에서 설명해요. 파일 시스템 백업은 원하는 어떤 방법을 써도 좋아요. 예를 들어:
tar -cf backup.tar /usr/local/pgsql/data
다만 이 방법을 비현실적이거나 적어도 pg_dump 방법보다 못하게 만드는 제약이 두 가지 있어요.
- 유용한 백업을 얻으려면 데이터베이스 서버를 반드시 종료해야 해요. 모든 접속을 금지하는 것 같은 절반만의 조치는 동작하지 않아요 (부분적으로는
tar같은 도구가 파일 시스템 상태의 원자적 스냅샷을 찍지 않기 때문이고, 서버 내부의 버퍼링 때문이기도 해요). 서버 중지에 대한 정보는 18.5절에서 찾아볼 수 있어요. 말할 것도 없이 데이터를 복원하기 전에도 서버를 종료해야 해요. - 데이터베이스의 파일 시스템 구성을 자세히 파봤다면, 개별 테이블이나 데이터베이스만 해당 파일이나 디렉터리에서 백업하거나 복원하려는 유혹이 들 수 있어요. 하지만 그건 동작하지 않아요. 그 파일들에 담긴 정보는 모든 트랜잭션의 커밋 상태를 담은 커밋 로그 파일
pg_xact/*가 없으면 쓸 수 없기 때문이에요. 테이블 파일은 이 정보가 있어야만 사용 가능해요. 물론 테이블 하나와 그에 딸린pg_xact데이터만 복원하는 것도 불가능한데, 그러면 데이터베이스 클러스터의 다른 모든 테이블을 쓸모없게 만들기 때문이에요. 그래서 파일 시스템 백업은 전체 데이터베이스 클러스터를 통째로 백업하고 복원할 때만 동작해요.
파일 시스템이 그 기능을 지원한다면(그리고 올바르게 구현됐다고 신뢰할 의향이 있다면) 데이터 디렉터리의 "일관된 스냅샷(consistent snapshot)"을 만드는 대안적 파일 시스템 백업 접근법도 있어요. 일반적인 절차는 데이터베이스가 들어 있는 볼륨의 "동결된 스냅샷(frozen snapshot)"을 만들고, 그 스냅샷에서 데이터 디렉터리 전체(일부가 아니라, 위를 보세요)를 백업 장치로 복사한 다음 동결된 스냅샷을 해제하는 거예요. 이 방법은 데이터베이스 서버가 실행 중이어도 동작해요. 다만 이렇게 만든 백업은 데이터베이스 서버가 제대로 종료되지 않은 상태로 데이터베이스 파일을 저장해요. 그래서 백업된 데이터로 서버를 시작하면 이전 서버 인스턴스가 충돌했다고 생각하고 WAL 로그를 재생해요. 이것은 문제가 아니에요. 그저 알고 있으면 돼요 (그리고 백업에 WAL 파일을 꼭 포함하세요). 스냅샷을 찍기 전에 CHECKPOINT를 실행하면 복구 시간을 줄일 수 있어요.
데이터베이스가 여러 파일 시스템에 퍼져 있다면 모든 볼륨의 정확히 동시적인 동결 스냅샷을 얻을 방법이 없을 수도 있어요. 예를 들어 데이터 파일과 WAL 로그가 다른 디스크에 있거나, 테이블스페이스가 다른 파일 시스템에 있다면, 스냅샷은 반드시 동시여야 하므로 스냅샷 백업이 불가능할 수 있어요. 이런 상황에서 일관된 스냅샷 기법을 믿기 전에 파일 시스템 문서를 아주 주의 깊게 읽으세요.
동시 스냅샷이 불가능하다면, 하나의 옵션은 모든 동결 스냅샷을 만들 수 있을 만큼 데이터베이스 서버를 충분히 오래 종료하는 거예요. 또 다른 옵션은 연속 아카이빙 베이스 백업(25.3.2절)을 수행하는 것이에요. 그런 백업은 백업 중 파일 시스템 변경에 면역이 있거든요. 이 방법은 백업 과정 동안에만 연속 아카이빙을 활성화하면 되고, 복원은 연속 아카이브 복구(25.3.5절)로 합니다.
또 다른 옵션은 rsync로 파일 시스템 백업을 수행하는 거예요. 먼저 데이터베이스 서버가 실행되는 동안 rsync를 돌리고, 그 다음 서버를 rsync --checksum을 수행할 만큼 충분히 오래 종료하는 식으로 해요. (--checksum이 필요한 이유는 rsync의 파일 수정 시각 세분성이 1초밖에 안 되기 때문이에요.) 두 번째 rsync는 전송할 데이터가 상대적으로 적기 때문에 첫 번째보다 빠르고, 서버가 종료됐으므로 최종 결과는 일관돼요. 이 방법은 최소한의 다운타임으로 파일 시스템 백업을 수행할 수 있게 해줘요.
파일 시스템 백업은 보통 SQL 덤프보다 크다는 점을 기억하세요. (예를 들어 pg_dump는 인덱스 내용은 덤프할 필요 없이 재생성 명령만 덤프해요.) 하지만 파일 시스템 백업을 만드는 게 더 빠를 수 있어요.
25.3. 연속 아카이빙과 시점 복구 (Continuous Archiving and Point-in-Time Recovery, PITR)
PostgreSQL은 항상 클러스터 데이터 디렉터리의 pg_wal/ 하위 디렉터리에 write ahead log(WAL)를 유지해요. 이 로그는 데이터베이스의 데이터 파일에 대한 모든 변경을 기록해요. 이 로그는 주로 크래시 안전 목적으로 존재해요. 시스템이 충돌하면 마지막 체크포인트 이후의 로그 항목을 "재생"해서 데이터베이스를 일관 상태로 복원할 수 있거든요. 그런데 이 로그의 존재 덕분에 데이터베이스 백업의 세 번째 전략을 쓸 수 있어요. 파일 시스템 레벨 백업과 WAL 파일 백업을 결합하는 거예요. 복구가 필요하면 파일 시스템 백업을 복원한 다음, 백업된 WAL 파일에서 재생해서 시스템을 현재 상태로 가져와요. 이 접근 방식은 앞의 두 방식보다 운영이 더 복잡하지만, 몇 가지 중요한 이점이 있어요.
- 완벽히 일관된 파일 시스템 백업을 시작점으로 요구하지 않아요. 백업의 내부 불일치는 로그 재생으로 교정돼요 (크래시 복구 때 일어나는 일과 크게 다르지 않아요). 그래서 파일 시스템 스냅샷 기능은 필요 없고 tar나 비슷한 아카이빙 도구만 있으면 돼요.
- 무한히 긴 WAL 파일 시퀀스를 재생에 결합할 수 있으므로, WAL 파일을 계속 아카이빙하기만 하면 연속 백업을 얻을 수 있어요. 이는 자주 전체 백업을 받기 어려운 대용량 데이터베이스에서 특히 값져요.
- WAL 항목을 끝까지 재생할 필요는 없어요. 어느 지점에서든 재생을 멈추고 그 시점의 데이터베이스 일관 스냅샷을 가질 수 있어요. 따라서 이 기법은 *시점 복구(point-in-time recovery)*를 지원해요. 베이스 백업을 받은 이후의 어떤 시점으로도 데이터베이스 상태를 복원할 수 있어요.
- 같은 베이스 백업 파일을 로드한 다른 머신에 WAL 파일 시퀀스를 계속 공급하면 웜 스탠바이(warm standby) 시스템이 돼요. 언제든 두 번째 머신을 띄우면 거의 최신 사본의 데이터베이스를 갖게 돼요.
참고: pg_dump와 pg_dumpall은 파일 시스템 레벨 백업을 만들지 않고, 연속 아카이빙 솔루션의 일부로 쓸 수 없어요. 그런 덤프는 논리적이고 WAL 재생에 쓸 충분한 정보를 담고 있지 않아요.
일반 파일 시스템 백업 기법과 마찬가지로 이 방법도 전체 데이터베이스 클러스터의 복원만 지원하고 일부만 지원하지는 않아요. 또한 아카이빙 저장 공간을 많이 요구해요. 베이스 백업은 부피가 클 수 있고, 바쁜 시스템은 아카이빙해야 할 수 메가바이트(수백 MB)의 WAL 트래픽을 만들어 내기 때문이에요. 그래도 높은 신뢰성이 필요한 많은 상황에서 선호되는 백업 기법이에요.
연속 아카이빙(많은 데이터베이스 벤더가 "온라인 백업"이라고도 불러요)으로 성공적으로 복구하려면, 백업 시작 시점까지 거슬러 올라가는 연속적인 아카이빙된 WAL 파일 시퀀스가 필요해요. 그래서 시작하려면 첫 베이스 백업을 받기 전에 WAL 파일을 아카이빙하는 절차를 설정하고 테스트해야 해요. 그래서 우리는 WAL 파일 아카이빙의 메커니즘부터 다룰게요.
25.3.1. WAL 아카이빙 설정하기 (Setting Up WAL Archiving)
추상적으로 보면, 실행 중인 PostgreSQL 시스템은 무한히 긴 WAL 레코드 시퀀스를 생산해요. 시스템은 이 시퀀스를 물리적으로 WAL 세그먼트 파일(segment file)로 나누는데, 보통 개당 16MB예요 (세그먼트 크기는 initdb 중에 바꿀 수 있어요). 세그먼트 파일에는 추상 WAL 시퀀스에서의 위치를 반영한 숫자 이름이 주어져요. WAL 아카이빙을 사용하지 않을 때 시스템은 보통 세그먼트 파일 몇 개만 만들고, 더 이상 필요 없는 세그먼트 파일을 더 높은 세그먼트 번호로 이름을 바꿔 "재활용"해요. 마지막 체크포인트보다 앞선 내용의 세그먼트 파일은 더 이상 관심 대상이 아니며 재활용될 수 있다고 가정해요.
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 파일 시퀀스는 사실상 데이터베이스 안의 모든 것을 담고 있으므로, 아카이빙된 데이터가 제3자의 눈으로부터 보호되도록 하고 싶을 거예요. 예를 들어 그룹이나 다른 사용자에게 읽기 권한이 없는 디렉터리로 아카이빙하세요.
아카이브 명령이 성공할 때만 0 종료 상태를 반환하는 게 중요해요. 0 결과를 받으면 PostgreSQL은 파일이 성공적으로 아카이빙됐다고 가정하고 그 파일을 제거하거나 재활용해요. 반면 0이 아닌 상태는 파일이 아카이빙되지 않았다는 뜻이고, 성공할 때까지 주기적으로 다시 시도해요.
아카이빙하는 또 다른 방법은 archive_library로 맞춤 아카이브 모듈을 사용하는 거예요. 그런 모듈은 C로 작성되므로, 직접 만드는 것은 셸 명령을 쓰는 것보다 훨씬 더 많은 노력이 들 수 있어요. 하지만 아카이브 모듈은 셸을 통한 아카이빙보다 더 나은 성능을 낼 수 있고, 유용한 서버 자원에 많이 접근할 수 있어요. 아카이브 모듈에 대한 자세한 내용은 제49장을 보세요.
아카이브 명령이 시그널(서버 종료의 일부로 쓰이는 SIGTERM은 제외)로 종료되거나, 명령을 찾을 수 없는 것 같은 종료 상태 125보다 큰 오류로 셸이 끝나거나, 아카이브 함수가 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 연산이 아니라 수동으로 편집되기 때문이에요. 구성 파일을 정기적인 파일 시스템 백업 절차로 백업되는 위치에 두는 것을 고려해 보세요. 구성 파일을 옮기는 법은 19.2절을 보세요.
아카이브 명령이나 함수는 완성된 WAL 세그먼트에만 호출돼요. 따라서 서버가 WAL 트래픽을 거의 만들지 않는다면(또는 그런 시간이 있다면) 트랜잭션 완료와 안전한 아카이브 저장소 기록 사이에 긴 지연이 있을 수 있어요. 아카이빙되지 않은 데이터가 얼마나 오래됐는지에 한도를 두려면 archive_timeout을 설정해서 서버가 적어도 그 주기마다 새 WAL 세그먼트 파일로 전환하도록 강제할 수 있어요. 강제 전환으로 일찍 아카빙된 파일도 완전히 찬 파일과 같은 길이라는 점을 주의하세요. 그래서 아주 짧은 archive_timeout을 설정하는 건 현명하지 않아요. 아카이브 저장 공간을 부풀리거든요. 대략 1분 정도의 archive_timeout 설정이 보통 합리적이에요.
또한 막 완료된 트랜잭션이 가능한 한 빨리 아카이빙되도록 하려면 pg_switch_wal로 세그먼트 전환을 수동으로 강제할 수 있어요. WAL 관리와 관련된 다른 유틸리티 함수는 표 9.97에 나열되어 있어요.
wal_level이 minimal일 때 일부 SQL 명령은 14.4.7절에서 설명된 대로 WAL 로깅을 피하도록 최적화돼요. 그런 문 중 하나가 실행되는 동안 아카이빙이나 스트리밍 복제가 켜져 있었다면, WAL에 아카이브 복구에 충분한 정보가 담기지 않아요. (크래시 복구는 영향을 받지 않아요.) 이런 이유로 wal_level은 서버 시작 시에만 변경할 수 있어요. 반면 archive_command와 archive_library는 구성 파일 리로드로 변경할 수 있어요. 셸로 아카이빙하면서 일시적으로 아카이빙을 멈추고 싶다면 archive_command를 빈 문자열('')로 설정하는 방법이 있어요. 그러면 동작하는 archive_command가 다시 설정될 때까지 pg_wal/에 WAL 파일이 쌓여요.
25.3.2. 베이스 백업 만들기 (Making a Base Backup)
베이스 백업을 수행하는 가장 쉬운 방법은 pg_basebackup 도구를 사용하는 거예요. 일반 파일로나 tar 아카이브로 베이스 백업을 만들 수 있어요. pg_basebackup이 제공하는 것보다 더 많은 유연성이 필요하면 저수준 API로도 베이스 백업을 만들 수 있어요 (25.3.4절 참조).
베이스 백업에 걸리는 시간에 대해 걱정할 필요는 없어요. 다만 평소 full_page_writes를 비활성화한 채 서버를 운영한다면, 백업 모드 동안 full_page_writes가 사실상 강제되므로 백업이 실행되는 동안 성능 저하를 느낄 수 있어요.
백업을 사용하려면 파일 시스템 백업이 진행되는 동안과 그 이후에 생성된 모든 WAL 세그먼트 파일을 보관해야 해요. 이를 돕기 위해 베이스 백업 과정은 파일 시스템 백업에 필요한 첫 번째 WAL 세그먼트 파일의 이름을 딴 *백업 히스토리 파일(backup history file)*을 만들고, 이를 즉시 WAL 아카이브 영역에 저장해요. 예를 들어 시작 WAL 파일이 0000000100001234000055CD라면 백업 히스토리 파일은 대략 0000000100001234000055CD.007C9330.backup처럼 이름 붙어요. (파일 이름의 두 번째 부분은 WAL 파일 안의 정확한 위치를 나타내며, 보통 무시해도 돼요.) 파일 시스템 백업과 백업 중 사용된 WAL 세그먼트 파일(백업 히스토리 파일에 명시됨)을 안전하게 아카이빙했다면, 숫자상 그보다 작은 이름의 모든 아카이빙된 WAL 세그먼트는 그 파일 시스템 백업을 복구하는 데 더 이상 필요 없으니 삭제할 수 있어요. 다만 데이터 복구를 확실히 하기 위해 백업 세트 몇 개는 보관해 두는 걸 고려해야 해요.
백업 히스토리 파일은 그저 작은 텍스트 파일이에요. pg_basebackup에 준 레이블 문자열과, 백업의 시작·종료 시각 및 WAL 세그먼트를 담고 있어요. 레이블로 관련 덤프 파일을 식별했다면, 아카이빙된 히스토리 파일만으로 어떤 덤프 파일을 복원할지 알 수 있어요.
마지막 베이스 백업까지의 모든 아카이빙된 WAL 파일을 보관해야 하므로, 베이스 백업 사이의 간격은 보통 아카이빙된 WAL 파일에 얼마나 많은 저장 공간을 쓰고 싶은지에 따라 정해야 해요. 복구가 필요할 때 복구에 얼마나 오래 걸릴 준비가 됐는지도 고려해야 해요. 시스템이 그 모든 WAL 세그먼트를 재생해야 하므로, 마지막 베이스 백업 이후 오랜 시간이 지났다면 꽤 걸릴 수 있어요.
25.3.3. 증분 백업 만들기 (Making an Incremental Backup)
pg_basebackup에 --incremental 옵션을 지정하면 증분 백업을 받을 수 있어요. --incremental의 인자로는 같은 서버의 이전 백업에 대한 백업 매니페스트(backup manifest)를 제공해야 해요. 결과 백업에서 비관계(non-relation) 파일은 전체가 포함되지만, 일부 관계 파일은 더 작은 증분 파일로 대체될 수 있어요. 증분 파일은 이전 백업 이후 변경된 블록만과 현재 버전의 파일을 재구성할 충분한 메타데이터를 담고 있어요.
어떤 블록을 백업해야 하는지 알아내기 위해 서버는 데이터 디렉터리 안의 pg_wal/summaries 디렉터리에 저장된 WAL 요약(WAL summaries)을 사용해요. 필요한 요약 파일이 없으면 증분 백업을 시도하는 것이 실패해요. 이 디렉터리의 요약은 이전 백업의 시작 LSN부터 현재 백업의 시작 LSN까지의 모든 LSN을 커버해야 해요. 서버는 현재 백업의 시작 LSN을 확립한 직후 WAL 요약을 찾으므로, 필요한 요약 파일이 디스크에 즉시 존재하지 않을 가능성이 크지만 서버는 누락된 파일이 나타날 때까지 기다려요. 이는 WAL 요약화 프로세스가 뒤처진 경우에도 도움이 돼요. 하지만 필요한 파일이 이미 제거됐거나 WAL 요약화가 충분히 빨리 따라잡지 못하면 증분 백업은 실패해요.
증분 백업을 복원할 때는 증분 백업 자체뿐 아니라, 증분 백업에서 생략된 블록을 공급하는 데 필요한 모든 이전 백업도 있어야 해요. 이 요구 사항에 대한 자세한 내용은 pg_combinebackup을 보세요. 클러스터의 체크섬 상태가 변경된 경우 pg_combinebackup 사용에는 제한이 있다는 점을 주의하세요. pg_combinebackup 제한 사항을 보세요.
전체 백업 사용에 필요한 모든 요구 사항이 증분 백업에도 적용된다는 점을 주의하세요. 예를 들어 파일 시스템 백업이 진행되는 동안과 이후에 생성된 모든 WAL 세그먼트 파일과 관련 WAL 히스토리 파일이 여전히 필요해요. 그리고 25.3.5절에서 설명된 대로 recovery.signal(또는 standby.signal)을 만들고 복구를 수행해야 해요. 복원 시점에 이전 백업을 사용할 수 있어야 하고 pg_combinebackup을 써야 한다는 요구 사항은 그 모든 것에 더해지는 추가 요구 사항이에요. PostgreSQL에는 이후 증분 백업을 복원하는 기준으로 어떤 백업이 여전히 필요한지 알아내는 내장 메커니즘이 없다는 점을 기억하세요. 전체/증분 백업 사이의 관계를 스스로 추적하고, 이후 증분 백업을 복원할 때 필요할 수 있는 이전 백업을 제거하지 않도록 반드시 확인해야 해요.
증분 백업은 보통 데이터의 상당 부분이 변하지 않거나 천천히 변하는 비교적 큰 데이터베이스에서만 의미가 있어요. 작은 데이터베이스는 증분 백업의 존재를 무시하고 그냥 전체 백업을 받는 게 더 단순해요. 전체가 격렬하게 수정되는 큰 데이터베이스에서는 증분 백업이 전체 백업보다 훨씬 작지 않을 거예요.
증분 백업은 재생이 이전 백업(그것이 의존하는)보다 나중의 체크포인트에서 시작해야만 가능해요. 프라이머리에서 증분 백업을 받으면 이 조건은 항상 충족돼요. 각 백업이 새 체크포인트를 촉발하거든요. 스탠바이에서 재생은 가장 최근의 재시작 지점(restartpoint)부터 시작해요. 따라서 스탠바이 서버의 증분 백업은 이전 백업 이후 활동이 아주 적어서 새 재시작 지점이 만들어지지 않았다면 실패할 수 있어요.
25.3.4. 저수준 API로 베이스 백업 만들기 (Making a Base Backup Using the Low Level API)
pg_basebackup으로 전체나 증분 베이스 백업을 받는 대신, 저수준 API로 베이스 백업을 받을 수 있어요. 이 절차는 pg_basebackup 방식보다 단계가 몇 개 더 있지만 상대적으로 단순해요. 이 단계들을 순서대로 실행하고, 다음 단계로 넘어가기 전에 각 단계의 성공을 확인하는 것이 아주 중요해요.
여러 백업을 동시에 실행할 수 있어요 (이 백업 API로 시작한 것과 pg_basebackup으로 시작한 것 모두).
- WAL 아카이빙이 활성화되어 동작 중인지 확인해요.
pg_backup_start를 실행할 권한(슈퍼유저, 또는 그 함수에EXECUTE를 부여받은 사용자)이 있는 사용자로 서버에 접속해(어느 데이터베이스든 상관없어요) 다음 명령을 실행해요.
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를 써서 최대한 빨리 끝나요.
3. tar나 cpio 같은 편리한 파일 시스템 백업 도구로 백업을 수행해요 (pg_dump나 pg_dumpall은 안 돼요). 이 작업 중 데이터베이스의 정상 동작을 멈추는 것은 필요하지도 않고 바람직하지도 않아요. 이 백업 중 고려할 사항은 25.3.4.1절을 보세요.
4. 이전과 같은 접속에서 다음 명령을 실행해요.
SELECT * FROM pg_backup_stop(wait_for_archive => true);
이것은 백업 모드를 종료해요. 프라이머리에서는 다음 WAL 세그먼트로의 자동 전환도 수행해요. 스탠바이에서는 WAL 세그먼트를 자동으로 전환할 수 없으므로, 프라이머리에서 pg_switch_wal을 실행해서 수동 전환하고 싶을 수 있어요. 전환하는 이유는 백업 구간 동안 쓰인 마지막 WAL 세그먼트 파일이 아카이빙될 준비가 되도록 하기 위해서예요.
pg_backup_stop은 세 개의 값이 있는 한 행을 반환해요. 그중 두 번째 필드는 백업의 루트 디렉터리에 backup_label이라는 이름의 파일로 써야 해요. 세 번째 필드는 비어 있지 않다면 tablespace_map이라는 이름의 파일로 써야 해요. 이 파일들은 백업이 동작하는 데 필수적이며 수정 없이 바이트 단위로 그대로 써야 해요. 바이너리 모드로 파일을 열어야 할 수도 있어요.
5. 백업 동안 활성 상태였던 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 파일이 포함되지 않아 불완전하고 복원할 수 없게 될 수 있거든요.
25.3.4.1. 데이터 디렉터리 백업하기 (Backing Up the Data Directory)
일부 파일 시스템 백업 도구는 복사하는 동안 복사 대상 파일이 변경되면 경고나 오류를 내요. 활성 데이터베이스의 베이스 백업을 받을 때 이 상황은 정상이지 오류가 아니에요. 다만 이런 종류의 불평과 진짜 오류를 구별할 수 있어야 해요. 예를 들어 일부 rsync 버전은 "사라진 원본 파일(vanished source files)"에 대해 별도의 종료 코드를 반환하는데, 이 종료 코드를 비오류 사례로 받아들이는 드라이버 스크립트를 쓸 수 있어요. 또한 일부 GNU tar 버전은 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.pid와 postmaster.opts도 제외하고 싶을 텐데, 이 파일들은 결국 이 백업을 사용할 postmaster가 아니라 실행 중인 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 파일이 얼마나 뒤까지 가는지 스스로 추적해야 해요. 일반적으로는 위의 연속 아카이빙 절차를 따르는 게 더 좋아요.
25.3.5. 연속 아카이브 백업으로 복구하기 (Recovering Using a Continuous Archive Backup)
좋아요, 최악의 상황이 벌어져서 백업에서 복구해야 한다고 해볼게요. 절차는 이래요.
- 서버가 실행 중이라면 중지해요.
- 공간이 있다면 클러스터 데이터 디렉터리 전체와 모든 테이블스페이스를 나중에 필요할 경우를 대비해 임시 위치로 복사해요. 이 예방 조치는 기존 데이터베이스의 복사본 두 개를 보관할 만큼 시스템에 충분한 빈 공간이 필요하다는 점을 유의하세요. 공간이 부족하다면 최소한 클러스터의
pg_wal하위 디렉터리 내용을 저장하세요. 시스템이 다운되기 전에 아카이빙되지 않은 WAL 파일이 있을 수 있거든요. - 클러스터 데이터 디렉터리 아래와 사용 중인 테이블스페이스의 루트 디렉터리 아래의 모든 기존 파일과 하위 디렉터리를 제거해요.
- 전체 백업을 복원하는 경우 데이터베이스 파일을 대상 디렉터리에 직접 복원할 수 있어요. 올바른 소유권(데이터베이스 시스템 사용자지
root가 아니에요!)과 올바른 권한으로 복원되도록 하세요. 테이블스페이스를 사용한다면pg_tblspc/의 심볼릭 링크가 올바르게 복원됐는지 확인하세요. - 증분 백업을 복원하는 경우에는 증분 백업과 그것이 직간접적으로 의존하는 모든 이전 백업을 복원을 수행하는 머신에 복원해야 해요. 이 백업들은 실행 중인 서버가 최종적으로 위치할 대상 디렉터리가 아니라 별도의 디렉터리에 둬야 해요. 이렇게 한 다음
pg_combinebackup을 사용해서 전체 백업과 그 이후의 모든 증분 백업에서 데이터를 끌어와 합성 전체 백업을 대상 디렉터리로 써내요. 위와 같이 권한과 테이블스페이스 링크가 올바른지 확인하세요. pg_wal/에 있는 파일을 제거해요. 그것들은 파일 시스템 백업에서 온 것이므로 현재 상태가 아니라 아마 구식일 거예요.pg_wal/을 전혀 아카이빙하지 않았다면 올바른 권한으로 다시 만들고, 이전에 심볼릭 링크로 설정했다면 그렇게 다시 설정하도록 주의하세요.- 2단계에서 저장한 아카이빙되지 않은 WAL 세그먼트 파일이 있으면
pg_wal/로 복사해요. (이동이 아니라 복사가 좋아요. 문제가 생겨 다시 시작해야 할 경우를 대비해 원본 파일을 그대로 두기 위해서요.) postgresql.conf에 복구 구성 설정을 넣고(19.5.5절 참조) 클러스터 데이터 디렉터리에recovery.signal파일을 만들어요. 복구가 성공했는지 확신할 때까지 보통 사용자가 접속하지 못하게pg_hba.conf를 임시로 수정하고 싶을 수도 있어요.- 서버를 시작해요. 서버는 복구 모드로 들어가 필요한 아카이빙된 WAL 파일을 차례로 읽어요. 외부 오류로 복구가 중단되면 서버는 그냥 재시작하면 되고 복구를 계속해요. 복구 프로세스가 끝나면 서버는
recovery.signal을 제거하고(나중에 실수로 복구 모드에 다시 들어가는 것을 방지) 정상 데이터베이스 운영을 시작해요. - 데이터베이스 내용을 살펴보고 원하는 상태로 복구됐는지 확인해요. 아니면 1단계로 돌아가요. 문제없다면
pg_hba.conf를 정상으로 복원해 사용자가 접속하게 해요.
이 모든 것의 핵심은 어떻게 복구할지, 복구를 어디까지 실행할지 기술하는 복구 구성을 설정하는 거예요. 반드시 지정해야 하는 단 한 가지는 restore_command인데, 이게 아카이빙된 WAL 세그먼트 파일을 어떻게 가져올지 PostgreSQL에 알려줘요. archive_command처럼 이것도 셸 명령 문자열이에요. 원하는 WAL 파일의 이름으로 바뀌는 %f와, WAL 파일을 복사할 경로 이름으로 바뀌는 %p를 담을 수 있어요. (경로 이름은 현재 작업 디렉터리, 즉 클러스터의 데이터 디렉터리를 기준으로 해요.) 명령에 실제 % 문자를 넣어야 한다면 %%를 써요. 가장 단순한 유용한 명령은 이렇게 생겼어요.
restore_command = 'cp /mnt/server/archivedir/%f %p'
이 명령은 이전에 아카이빙된 WAL 세그먼트를 /mnt/server/archivedir 디렉터리에서 복사해요. 물론 훨씬 더 복잡한 것도 쓸 수 있어요. 운영자에게 적절한 테이프를 마운트하라고 요청하는 셸 스크립트일 수도 있죠.
명령이 실패 시 0이 아닌 종료 상태를 반환하는 것이 중요해요. 아카이브에 없는 파일을 요청받는 일도 있을 테니, 그렇게 요청받으면 0이 아닌 값을 반환해야 해요. 이것은 오류 조건이 아니에요. 예외는 명령이 시그널(데이터베이스 서버 종료의 일부로 쓰이는 SIGTERM은 제외)로 종료되거나 셸 오류(명령을 찾을 수 없는 경우 등)가 있을 때인데, 이때는 복구가 중단되고 서버가 시작되지 않아요.
요청되는 파일이 전부 WAL 세그먼트 파일은 아니에요. .history 접미사가 붙은 파일 요청도 예상해야 해요. 또한 %p 경로의 기본 이름(base name)은 %f와 다르다는 점을 알아두세요. 서로 바꿔 쓸 수 있다고 기대하지 마세요.
아카이브에서 찾을 수 없는 WAL 세그먼트는 pg_wal/에서 찾아봐요. 이는 최근의 아카이빙되지 않은 세그먼트를 쓸 수 있게 해줘요. 다만 아카이브에서 가용한 세그먼트는 pg_wal/의 파일보다 우선해요.
보통 복구는 가용한 모든 WAL 세그먼트를 진행해서 데이터베이스를 현재 시점(또는 가용한 WAL 세그먼트로 가능한 한 가까운 시점)으로 복원해요. 그래서 정상 복구는 "파일을 찾을 수 없음(file not found)" 메시지로 끝나요. 정확한 오류 메시지 텍스트는 restore_command 선택에 따라 달라요. 또한 복구 시작 시 00000001.history 같은 이름의 파일에 대한 오류 메시지를 볼 수도 있어요. 이것도 정상이며 단순 복구 상황에서는 문제를 나타내지 않아요. 논의는 25.3.6절을 보세요.
이전 시점으로 복구하고 싶다면(예: 주니어 DBA가 메인 트랜잭션 테이블을 떨어뜨리기 직전으로) 필요한 중지 지점(stopping point)을 지정하면 돼요. "복구 대상(recovery target)"이라 불리는 이 중지 지점은 날짜/시간, 이름 붙은 복원 지점(named restore point), 또는 특정 트랜잭션 ID 완료로 지정할 수 있어요. 글을 쓰는 현재 시점에는 어떤 트랜잭션 ID를 쓸지 정확히 식별할 도구가 없어서 날짜/시간과 이름 붙은 복원 지점 옵션만 실용적이에요.
참고: 중지 지점은 베이스 백업의 종료 시각, 즉
pg_backup_stop의 종료 시각 이후여야 해요. 베이스 백업이 진행 중이던 시점으로 복구하는 데 그 백업을 쓸 수는 없어요. (그런 시점으로 복구하려면 이전 베이스 백업으로 돌아가 거기서부터 앞으로 롤포워드해야 해요.)
복구가 손상된 WAL 데이터를 만나면 그 지점에서 복구가 멈추고 서버가 시작되지 않아요. 그런 경우 복구 프로세스를 처음부터 다시 실행하되, 손상 지점 이전의 "복구 대상"을 지정해서 복구가 정상적으로 끝나게 할 수 있어요. 시스템 크래시나 WAL 아카이브 접근 불가 같은 외부 이유로 복구가 실패하면, 간단히 재시작하면 되고 실패한 지점 근처에서 다시 시작해요. 복구 재시작은 정상 운영의 체크포인트와 비슷하게 동작해요. 서버가 주기적으로 모든 상태를 디스크로 강제하고, 이미 처리된 WAL 데이터를 다시 스캔할 필요가 없도록 pg_control 파일을 갱신해요.
25.3.6. 타임라인 (Timelines)
데이터베이스를 이전 시점으로 복원하는 능력은 타임 트래블과 평행 우주에 관한 공상과학 이야기 같은 복잡성을 만들어 내요. 예를 들어 데이터베이스의 원래 이력에서 화요일 저녁 5:15에 중요한 테이블을 떨어뜨렸지만 수요일 정오까지 그 실수를 깨닫지 못했다고 해볼게요. 당황하지 않고 백업을 꺼내 화요일 저녁 5:14 시점으로 복원해서 다시 실행하고 있어요. 데이터베이스 우주의 이 이력에서는 테이블을 떨어뜨린 적이 없어요. 그런데 이게 좋은 생각이 아니었다는 걸 나중에 깨닫고 원래 이력의 수요일 아침 어느 시점으로 돌아가고 싶다고 해볼게요. 데이터베이스가 실행 중이던 동안, 돌아가고 싶은 그 시점에 이르기까지의 WAL 세그먼트 파일 일부를 덮어썼다면 그렇게 할 수 없어요. 그래서 이를 피하려면, 시점 복구를 수행한 후 생성된 WAL 레코드 시퀀스와 원래 데이터베이스 이력에서 생성된 것을 구별해야 해요.
이 문제를 다루기 위해 PostgreSQL에는 타임라인(timelines) 개념이 있어요. 아카이브 복구가 완료될 때마다 그 복구 이후에 생성된 WAL 레코드 시퀀스를 식별하는 새 타임라인이 만들어져요. 타임라인 ID 번호는 WAL 세그먼트 파일 이름의 일부라서, 새 타임라인이 이전 타임라인들이 생성한 WAL 데이터를 덮어쓰지 않아요. 예를 들어 WAL 파일 이름 0000000100001234000055CD에서 앞의 00000001은 16진수 타임라인 ID예요. (서버 로그 메시지 같은 다른 맥락에서는 타임라인 ID가 보통 10진수로 출력된다는 점을 주의하세요.)
사실 많은 서로 다른 타임라인을 아카이빙하는 게 가능해요. 쓸모없는 기능 같아 보일 수도 있지만, 종종 생명을 구해요. 정확히 어떤 시점으로 복구할지 확신이 없어서 옛 이력에서 가지를 치기 좋은 지점을 찾을 때까지 시점 복구를 여러 번 시행착오해야 하는 상황을 생각해 보세요. 타임라인이 없다면 이 과정은 곧 다루기 어려운 난장판을 만들 거예요. 타임라인이 있으면 이전에 버렸던 타임라인 가지의 상태를 포함해 어떤 이전 상태로든 복구할 수 있어요.
새 타임라인이 만들어질 때마다 PostgreSQL은 어떤 타임라인에서 언제 가지를 쳤는지 보여주는 "타임라인 이력(timeline history)" 파일을 만들어요. 이 이력 파일들은 여러 타임라인이 담긴 아카이브에서 복구할 때 시스템이 올바른 WAL 세그먼트 파일을 고르는 데 필요해요. 그래서 WAL 세그먼트 파일처럼 WAL 아카이브 영역에 아카이빙돼요. 이력 파일은 그저 작은 텍스트 파일이라서 (크기가 큰 세그먼트 파일과 달리) 무기한 보관하는 게 싸고 적절해요. 원한다면 이 특정 타임라인이 왜 어떻게 만들어졌는지에 대한 자신의 메모를 이력 파일에 주석으로 추가할 수 있어요. 실험 결과로 여러 타임라인의 덤불이 생겼다면 그런 주석이 특히 가치 있어요.
복구의 기본 동작은 아카이브에서 찾은 최신 타임라인으로 복구하는 거예요. 베이스 백업을 받을 당시의 타임라인으로 복구하거나 특정 자식 타임라인으로 복구하려면(즉 복구 시도 후 생성된 어떤 상태로 돌아가려면) recovery_target_timeline에 current 또는 대상 타임라인 ID를 지정해야 해요. 베이스 백업보다 이른 시기에 갈라진 타임라인으로는 복구할 수 없어요.
25.3.7. 팁과 예시 (Tips and Examples)
연속 아카이빙을 구성하는 몇 가지 팁을 여기에 모았어요.
25.3.7.1. 독립형 핫 백업 (Standalone Hot Backups)
PostgreSQL의 백업 기능으로 독립형 핫 백업을 만들 수 있어요. 시점 복구에는 쓸 수 없지만, 보통 pg_dump 덤프보다 백업·복원이 훨씬 빠른 백업이에요. (pg_dump 덤프보다 훨씬 크기도 해서 어떤 경우에는 속도 장점이 상쇄될 수도 있어요.)
베이스 백업과 마찬가지로 독립형 핫 백업의 가장 쉬운 방법은 pg_basebackup 도구를 쓰는 거예요. 호출할 때 -X 매개변수를 포함하면 백업을 쓰는 데 필요한 모든 write-ahead log가 백업에 자동으로 포함되고, 백업 복원에 특별한 조치가 필요 없어요.
25.3.7.2. 압축된 아카이브 로그 (Compressed Archive Logs)
아카이브 저장 용량이 걱정된다면 gzip으로 아카이브 파일을 압축할 수 있어요.
archive_command = 'gzip < %p > /mnt/server/archivedir/%f.gz'
그러면 복구 중에 gunzip을 사용해야 해요.
restore_command = 'gunzip < /mnt/server/archivedir/%f.gz > %p'
25.3.7.3. archive_command 스크립트 (archive_command Scripts)
많은 사람이 archive_command를 정의할 때 스크립트를 써서 postgresql.conf 항목을 아주 단순하게 만들기로 해요.
archive_command = 'local_backup_script.sh "%p" "%f"'
아카이빙 과정에서 명령을 두 개 이상 쓰고 싶을 때마다 별도 스크립트 파일을 쓰는 게 좋아요. 모든 복잡성을 스크립트 안에서 관리할 수 있고, bash나 perl 같은 널리 쓰이는 스크립트 언어로 작성할 수 있거든요.
스크립트 안에서 해결할 수 있는 요구 사항의 예는 다음과 같아요.
- 안전한 오프사이트 데이터 저장소로 데이터 복사
- WAL 파일을 하나씩이 아니라 3시간마다 묶어서 전송하는 배치 처리
- 다른 백업·복구 소프트웨어와의 연동
- 오류를 보고하는 모니터링 소프트웨어와의 연동
팁:
archive_command스크립트를 쓸 때는logging_collector를 활성화하는 것이 좋아요. 스크립트가 stderr로 쓴 어떤 메시지도 데이터베이스 서버 로그에 나타나서, 실패 시 복잡한 구성을 쉽게 진단할 수 있게 해줘요.
25.3.8. 주의 사항 (Caveats)
글을 쓰는 현재 시점에서 연속 아카이빙 기법에는 몇 가지 제한이 있어요. 이것들은 아마 향후 릴리스에서 고쳐질 거예요.
- 베이스 백업을 받는 동안
CREATE DATABASE명령이 실행되고, 그 다음 베이스 백업이 진행 중인 동안CREATE DATABASE가 복사한 템플릿 데이터베이스가 수정되면, 복구가 그 수정사항을 생성된 데이터베이스에도 전파할 가능성이 있어요. 당연히 바람직하지 않죠. 이 위험을 피하려면 베이스 백업을 받는 동안 템플릿 데이터베이스를 수정하지 않는 것이 가장 좋아요. CREATE TABLESPACE명령은 문자 그대로의 절대 경로로 WAL 기록되므로, 같은 절대 경로의 테이블스페이스 생성으로 재생돼요. WAL이 다른 머신에서 재생된다면 바람직하지 않을 수 있어요. 같은 머신에서 새 데이터 디렉터리로 재생돼도 위험할 수 있어요. 재생이 여전히 원래 테이블스페이스의 내용을 덮어쓰거든요. 이런 종류의 잠재적 함정을 피하려면 테이블스페이스를 만들거나 없앤 후 새 베이스 백업을 받는 것이 가장 좋은 방법이에요.
또한 기본 WAL 형식은 많은 디스크 페이지 스냅샷을 포함하기 때문에 꽤 부피가 크다는 점을 알아둬야 해요. 이 페이지 스냅샷들은 부분적으로 쓰인 디스크 페이지를 고쳐야 할 수도 있으므로 크래시 복구를 지원하도록 설계됐어요. 시스템 하드웨어와 소프트웨어에 따라 부분 쓰기의 위험은 무시할 만큼 작을 수 있는데, 그 경우 full_page_writes 매개변수로 페이지 스냅샷을 꺼서 아카이빙된 WAL 파일의 총량을 크게 줄일 수 있어요. (그렇게 하기 전에 제28장의 주석과 경고를 읽으세요.) 페이지 스냅샷을 꺼도 PITR 연산에 WAL을 쓰지 못하는 것은 아니에요. 향후 개발 영역은 full_page_writes가 켜져 있을 때도 불필요한 페이지 복사본을 제거해서 아카이빙된 WAL 데이터를 압축하는 거예요. 그동안 관리자는 체크포인트 간격 매개변수를 가능한 한 늘려서 WAL에 포함되는 페이지 스냅샷 수를 줄이길 원할 수도 있어요.