2024. 11. 10. 21:12ㆍData Engineering
들어가며
제가 속한 데이터 팀에서는 운영을 위한 별도의 PostgreSQL DB를 사용하고 있는데요, 지난 주 팀장님으로부터 DB의 백업 업무를 받게되었습니다. 오늘은 소중한 데이터를 불의의 사고(!)로 부터 지키기 위한 PostgreSQL 백업 방식과 이를 젠킨스로 자동화한 방법에 대해서 공유해보려고 합니다.
1. 콜드 백업과 핫 백업
처음 업무를 받았을 때 "콜드 백업이 아닌 핫 백업"으로 진행해달라는 요청이 있었습니다. RDB 백업은 처음이었기 때문에 용어부터 생소했던 기억이 나는데요 우선 개념을 알아보겠습니다.
1 ) 콜드 백업
데이터 베이스 서버를 내린 상태에서 진행하는 백업입니다. 오프라인 백업, 닫힌 백업이라고도 합니다. 주로 유지 관리 시간에 실행되며 백업을 하는 동안 서비스가 불가능합니다.
2 ) 핫 백업
콜드 백업과는 반대로 데이터 베이스 서버를 중단하지 않고 진행하는 백업입니다. 온라인 백업, 열린 백업이라고도 합니다. 24시간 운영되어야 하는 서비스, 즉 무중단 가용성이 중요한 상황에서 선택합니다.
운영 시 사용하는 DB에는 ETL 작업에 필요한 쿼리도 들어있어 핫 백업을 요청 주신 것 같습니다. 그러면 PostgreSQL에서는 어떻게 핫 백업을 할 수 있는지 알아보겠습니다.
2. PostgreSQL의 백업
PostgreSQL의 백업 방식은 3가지가 있습니다.
1 ) SQL Dump
첫번째는 SQL문을 포함하는 dump file을 생성해 백업하는 방식입니다. 단일 데이터 베이스를 덤프 하고 싶은 경우 pg_dump를, 사용자 및 테이블 스페이스를 포함해 클러스터 전체를 덤프 하고 싶은 경우 pg_dumpall 사용할 수 있습니다.
✔️ 장점
- pg_dump의 결과를 최신 버전의 PostgreSQL로 로드할 수 있습니다.
- pg_dump 동안에 다른 작업을 막지 않습니다(단, ALTER TABLE과 같은 배타적 잠금을 사용하는 경우 제외).
- pg_dump는 표준 출력을 사용하기 때문에 대규모 DB 백업 시 압축이 가능
✔️ 단점
- 백업시 SQL 문을 실행해야 하기 때문에 속도가 느릴 수 있습니다.
추가로 pg_dump를 사용하는 경우 dump file을 복원하기 전에 덤프 된 객체를 소유하거나 객체에 대한 권한이 부여된 모든 사용자가 이미 존재해야 합니다. 명령어는 아래와 같이 간단하게 실행 가능합니다.
# dump 사용한 백업
pg_dump dbname > dumpfile
# dump 사용한 복구
psql dbname < dumpfile
1 ) File System Level Backup
데이터를 저장하는데 사용하는 파일, 즉 `$PGDATA` 경로의 전체 파일을 복제해 백업하는 방식입니다.
✔️ 장점
- SQL dump 방식과 비교했을 때 빠르게 백업할 수 있습니다.
✔️ 단점
- 데이터 베이스 서버를 중단해야 하는 콜드 백업 방식입니다.
- 특정 테이블 또는 데이터 베이스가 아닌 전체 클러스터 백업만 가능합니다.
- 여러 파일 시스템에 데이터가 분산되어 있는 경우 정확히 동시에 스냅샷 파일을 생성하는 것이 어렵습니다.
오프라인 백업이기 때문에 온라인 상태에서의 백업이 필요하다면 `pg_basebackup`를 사용할 수 있습니다.
3 ) Archiving Backup
WAL 파일과 파일 시스템 백업을 결합한 방식입니다. PostgreSQL은 클러스터의 하위 폴더에 WAL(Write Ahead Log)이라는 파일을 생성하는데, 이는 클러스터의 모든 변경 사항이 기록된 파일입니다. 원래의 목적은 트랜잭션 간의 충돌을 막기 위함이지만 베이스 백업 파일과 함께 사용하면 *특정 시점으로 백업할 수 있습니다.
*PITR(point-in-time recovery, 시점 복구)
✔️ 장점
- 데이터 베이스 서버를 중단하지 않아도 되는 핫 백업 방식입니다.
- 복구를 위해 WAL 파일을 사용할 수 있어 잦은 전체 백업이 어려운 대규모 데이터 베이스에 적합합니다.
- 다른 머신에 베이스 백업 파일을 생성하고 WAL 파일을 지속적으로 보내면 웜 스탠바이 시스템 구축이 가능합니다.
✔️ 단점
- 파일 시스템 백업과 마찬가지로 전체 클러스터의 백업만 가능합니다.
PostgreSQL에서 핫 백업을 하기 위해서라면 아카이빙 백업 방식을 사용할 수 있을 것 같습니다. 과정을 하나하나 알아보겠습니다.
3. Archivig Backup 적용
1 ) WAL 보관 활성화
실행 중인 PostgreSQL은 긴 WAL 시퀀스를 생성하고 순서대로 번호를 붙여 16MB의 세그먼트 파일에 나눕니다. WAL 보관을 활성화하지 않을 때 DB는 더 이상 필요 없는 파일을 더 높은 번호로 재활용하는데, 시점 복구를 위해서는 basebackup부터 쌓인 연속적인 WAL 파일이 필요합니다. 따라서 conf 파일에서 WAL 보관을 활성화해야 합니다.
- wal_level: replica 이상으로 변경
- archive_mode: on으로 변경
- archive_command: WAL 아카이빙을 위한 명령어
- 보관한 WAL 파일을 재보관하지 않도록 test 사용
- 명령어에서 %f는 파일명, %p는 경로 사용 가능
- 보안을 위해 WAL파일이 보관되는 디렉토리는 전체 읽기 액세스 권한이 없도록 설정

*conf 파일의 경로를 찾기 위해 `SHOW config_file;` 명령어를 사용
pg_settings 데이터에 쿼리 해서 archive_command 가 정상적으로 들어갔는지 확인할 수 있습니다.

2 ) 베이스 백업 생성
`pg_basebackup` 명령어를 사용해서 베이스 백업을 생성할 수 있습니다. -Ft 옵션을 사용하면 tar 형식으로 파일을 생성할 수 있습니다. 아래 명령어를 사용해 백업 시 backup_manifest, base.tar.gz, pg_wal.tar.gz 세 파일이 생성됩니다.
pg_basebackup -U datateam -D /tmp -Ft -z -P

베이스 백업을 하는 동안에도 WAL 파일이 생성될 수 있는데요, 이는 백업에 필요한 첫 번째 WAL 파일의 이름에 .backup이 붙어 WAL 아카이빙 경로에 생성됩니다. 베이스 백업 파일과 WAL 파일이 안전하게 보관되었다면 이전 숫자가 붙은 WAL 파일은 삭제해도 됩니다. 별도의 관리가 없다면 파일은 계속해서 쌓이기 때문에, 서비스에 따라 특정 기간 또는 파일 개수를 기준으로 삭제 정책을 정할 수 있습니다.
만약 베이스 백업 시 과거의 WAL 파일을 삭제하고 싶다면 아래와 같은 스크립트를 만들어 cron으로 실행할 수도 있습니다.
#!/bin/bash
ARCHIVE_DIR="$PGDATA/backup/archive"
LAST_WAL=$(ls -t $ARCHIVE_DIR | head -1) # 가장 최근 WAL 파일
# pg_archivecleanup을 사용해 과거의 WAL 파일 삭제
pg_archivecleanup $ARCHIVE_DIR $LAST_WAL
# 과거의 .backup 파일 삭제
find $ARCHIVE_DIR -name "*.backup" ! -newer "$ARCHIVE_DIR/$LAST_REQUIRED_WAL" ! -name "$LAST_REQUIRED_WAL" -type f -exec rm {} \;
4. 백업 방식 선택
이렇게 PostgreSQL의 백업 방식을 알아보고 팀 내 공유를 드렸는데요 최종적으로 WAL 파일은 별도로 보관하지 않고 매일 새벽 베이스 백업 파일만 생성하기로 했습니다. 운영용 DB의 경우 잦은 트랜잭션이 일어나지 않기 때문이라고 생각됩니다. 매일 직접 베이스 파일을 생성할 수 없기 때문에 이 과정을 자동화해 보도록 하겠습니다.
5. 백업 자동화
간단한 스케줄링 작업은 팀 내에서 젠킨스(Jenkins)를 사용하고 있어, 백업 작업도 하나의 쉘 스크립트로 만들어 실행하기로 했습니다.
1 ) 베이스 백업 생성
우선 PostgreSQL 서버 정보를 사용해 pg_basebackup 명령어를 실행합니다.
pg_basebackup -U $USER_NAME --no-password -h $POSTGRES_HOST -p $POSTGRES_PORT -D $DAILY_BACKUP_DIR -Ft -z -P
이때 젠킨스에서 명령어를 실행할 수 있도록 설정 파일 `pg_hba.conf`에 IP 주소와 사용자 정보를 추가하고, 사용자에 복제 권한을 부여해야 합니다.
ALTER USER [USER_NAME] WITH REPLICATION;
2 ) 파일 압축 암호화
생성된 백업 파일은 보안을 위해 키를 사용해서 압축합니다. `openssl` 명령어로 AES-256 알고리즘을 사용해 암호화했습니다.
KEY=$(openssl rand -hex 32)
openssl enc -e -aes-256-cbc -salt -in $ZIP_FILE -out $ENC_FILE -k $KEY
3 ) Slack 채널 암호화 키 전송
암호화에 사용된 키는 제한된 인원만 접근할 수 있는 Slack 채널로 전송하였습니다. 전송할 때에는 GCS 경로를 같이 메시지로 담았습니다.
MESSAGE="$GCS_PROPJECT_NAME/$GCS_BUCKET_NAME/$DATE/$ENC_FILE File Password : $KEY"
curl -X POST --data "payload={\"text\":\"$MESSAGE\"}" "$SLACK_WEBHOOK_URL"
4 ) GCS에 압축 파일 업로드
생성된 압축 파일은 GCS 버킷 일자별 폴더에 생성하였습니다. 파일 수명 주기를 30일로 설정해 최근 한 달간의 백업 파일만 유지할 수 있도록 했습니다.
gcloud auth activate-service-account --key-file=$GOOGLE_APPLICATION_CREDENTIALS
gcloud storage cp $ENC_FILE gs://$GCS_BUCKET_NAME/$DATE/$ENC_FILE
위 과정을 포함하는 전체 스크립트는 아래와 같습니다.
CONDA_ENV_NAME="[CONDA_ENV_NAME]"
POSTGRES_HOST="[POSTGRES_HOST]"
POSTGRES_PORT="[POSTGRES_PORT]"
DATE=$(date +"%Y%m%d")
BACKUP_DIR="/var/jenkins_home/pgsql/backups"
DAILY_BACKUP_DIR=$BACKUP_DIR/$DATE
ZIP_FILE="backup_$DATE.zip"
ENC_FILE=$ZIP_FILE.enc
GOOGLE_APPLICATION_CREDENTIALS="[KEY_PATH]"
GCS_PROPJECT_NAME="[GCS_PROPJECT_NAME]"
GCS_BUCKET_NAME="[GCS_BUCKET_NAME]"
SLACK_WEBHOOK_URL="[SLACK_WEBHOOK_URL]"
# 1. Conda 환경 활성화
. /apps/miniconda3/etc/profile.d/conda.sh > /dev/null 2>&1
conda activate $CONDA_ENV_NAME > /dev/null 2>&1
# 2. PostgreSQL 백업 파일 생성
pg_basebackup -U datateam --no-password -h $POSTGRES_HOST -p $POSTGRES_PORT -D $DAILY_BACKUP_DIR -Ft -z -P
# 3. 백업 파일을 ZIP으로 압축
zip -r $BACKUP_DIR/$ZIP_FILE $DAILY_BACKUP_DIR
# 3. OpenSSL을 사용하여 ZIP 파일 암호화
KEY=$(openssl rand -hex 32)
cd $BACKUP_DIR
openssl enc -e -aes-256-cbc -salt -in $ZIP_FILE -out $ENC_FILE -k $KEY
# 4. Slack 채널로 암호화 키 전송
MESSAGE="$GCS_PROPJECT_NAME/$GCS_BUCKET_NAME/$DATE/$ENC_FILE File Password : $KEY"
curl -X POST --data "payload={\"text\":\"$MESSAGE\"}" "$SLACK_WEBHOOK_URL"
# 5. Google Cloud Storage에 암호화된 파일 업로드
gcloud auth activate-service-account --key-file=$GOOGLE_APPLICATION_CREDENTIALS
gcloud storage cp $ENC_FILE gs://$GCS_BUCKET_NAME/$DATE/$ENC_FILE
# 6. 과거 백업 파일 및 임시 파일 삭제
find $BACKUP_DIR -name "backup_*.zip.enc" -type f -not -name "$ENC_FILE" -delete
rm -r $BACKUP_DIR/$ZIP_FILE $DAILY_BACKUP_DIR
✔️ 실행 결과


마무리
안정적인 PostgreSQL 운영을 위한 백업 방식과 자동화 과정에 대해서 알아봤습니다. PostgreSQL은 이 회사에서 처음 사용해 보았는데요, 백업을 진행하며 조금이나마 더 친해진 것 같아요. 복구 과정도 스크립트로 만들어보면 좋을 것 같습니다!
내용 중 부족한 부분이나 더 나은 방법이 있다면 언제든 말씀해 주세요. 읽어주셔서 감사합니다.
참고 자료
pg_basebackup
pg_basebackup pg_basebackup — take a base backup of a PostgreSQL cluster Synopsis pg_basebackup [option...] Description pg_basebackup is used to take …
www.postgresql.org
'Data Engineering' 카테고리의 다른 글
GCP에서 VPC 방화벽 규칙을 설정해보자 🔐 (0) | 2025.03.30 |
---|---|
[PROJECT] 나만의 작고 소중한 데이터 파이프라인 만들기🌱 | 책방 사장님의 큐레이션을 위한 대시보드 구축기 (1) | 2024.03.09 |