1인 회사 시스템 build

운영 DB 안 건드리고 백업 복구 리허설하기

백업 파일이 쌓이는 것과 그걸로 서버가 살아나는 것은 다른 문제입니다. 운영 데이터베이스에 손대지 않고 임시 DB로 복구를 리허설하는 5단계 절차와 명령어, 그리고 18MB 덤프 기준 10초 실측을 정리합니다.


지난번에 한 달 동안 조용히 안 돌던 백업 이야기를 썼습니다. heartbeat를 붙여서 백업이 매일 도는 것까지는 확인하게 됐습니다.

그런데 도는 것과 살아나는 것은 다른 문제입니다. 백업 파일이 매일 쌓여도, 그걸로 서버를 되살려본 적이 없다면 복원이 되는지는 아무도 모릅니다. 암호화 키가 틀렸을 수도 있고, 덤프가 깨졌을 수도 있고, 복원 절차 자체를 모를 수도 있습니다. 장애가 난 날 처음 알게 되는 게 최악입니다.

그래서 미뤄두던 복구 리허설을 처음 해봤습니다. 결론부터 말하면, 18MB 덤프 기준으로 전 과정이 10초였습니다.

원칙 · 운영 DB에는 손대지 않는다

복구 문서를 보면 대부분 pg_restore --clean 처럼 기존 DB를 밀고 덮어쓰는 명령이 나옵니다. 리허설에 이걸 그대로 쓰면 운영 데이터가 위험해집니다.

리허설의 원칙은 하나입니다. 임시 DB를 새로 만들어 거기에 복원하고, 확인 후 지운다. 같은 PostgreSQL 컨테이너 안이라도 별도 데이터베이스면 운영 데이터와 완전히 분리됩니다.

절차 · 5단계

제 백업 구성은 pg_dump → openssl 암호화 → Backblaze B2 업로드입니다. 리허설은 그 역순입니다.

1. 최신 백업 내려받기

rclone은 설정 파일 없이 환경변수만으로 B2에 붙을 수 있습니다.

export RCLONE_CONFIG_B2_TYPE=b2
export RCLONE_CONFIG_B2_ACCOUNT="$B2_KEY_ID"
export RCLONE_CONFIG_B2_KEY="$B2_APP_KEY"

rclone copyto \
  "b2:$B2_BUCKET/postgres/calm-core-calm_dev-20260719-030000.dump.enc" \
  ~/restore-test/latest.dump.enc

18MB 기준 6초 걸렸습니다.

2. 복호화

백업할 때 쓴 것과 같은 파라미터로 풀어야 합니다. 키는 인자로 넘기지 않고 파일 디스크립터로 전달해 셸 히스토리·프로세스 목록에 남지 않게 합니다.

openssl enc -d -aes-256-cbc -md sha512 -pbkdf2 -iter 200000 \
  -in ~/restore-test/latest.dump.enc \
  -out ~/restore-test/restore.dump \
  -pass fd:3 3<<<"$BACKUP_ENCRYPTION_KEY"

file ~/restore-test/restore.dump
# PostgreSQL custom database dump - v1.15-0

file 명령으로 유효한 덤프인지부터 확인합니다. 여기서 형식이 안 나오면 키나 파라미터가 틀린 것입니다.

3. 임시 DB에 복원

docker exec -e PGPASSWORD="$DB_PASSWORD" calm-prod-postgres \
  psql -U postgres -c "CREATE DATABASE calm_restore_test"

docker exec -i -e PGPASSWORD="$DB_PASSWORD" calm-prod-postgres \
  pg_restore -U postgres -d calm_restore_test \
  --no-owner --no-privileges < ~/restore-test/restore.dump

--clean 없이, 방금 만든 빈 임시 DB에만 복원합니다. 4초 걸렸습니다.

4. 운영 DB와 대조

테이블 수와 주요 테이블의 행 수를 나란히 봅니다.

for db in calm_restore_test calm_dev; do
  docker exec -e PGPASSWORD="$DB_PASSWORD" calm-prod-postgres \
    psql -U postgres -d $db -t -A -c \
    "SELECT relname || '=' || n_live_tup
     FROM pg_stat_user_tables
     ORDER BY n_live_tup DESC LIMIT 6"
done

제 결과는 이랬습니다.

테이블복원본 (03:00 스냅샷)운영 (당일 오후)
research_items8,1548,366
rate_limits6,0806,080
llm_jobs5,1585,434

두 DB 모두 테이블 62개. 행 수 차이는 전부 백업 시각(새벽 3시) 이후 그날 쌓인 활동분이었습니다. 차이가 “백업이 깨져서”가 아니라 “시점이 달라서”로 설명되면 성공입니다.

5. 정리

docker exec -e PGPASSWORD="$DB_PASSWORD" calm-prod-postgres \
  psql -U postgres -c "DROP DATABASE calm_restore_test"
rm -rf ~/restore-test

임시 DB와 내려받은 파일을 지우면 서버는 리허설 전과 완전히 같은 상태입니다.

실측 정리

  • 덤프 크기 18MB (암호화 후)
  • 다운로드 6초 · 복호화 1초 미만 · 복원 4초
  • 테이블 62개 전부 복원, 행 수 검증 통과

작은 DB라 이 정도지, 수십 GB면 다운로드와 복원 시간이 본격적으로 걸립니다. 그때는 “복구에 몇 분 걸리는가” 자체가 리허설로만 알 수 있는 숫자가 됩니다. 장애 상황에서 “복원 돌리면 40분”을 아는 것과 모르는 것은 대응이 완전히 다릅니다.

리허설 주기는 백업 구성이 바뀔 때마다 한 번, 그리고 분기에 한 번이면 충분하다고 보고 있습니다. 다음 리허설 때는 이 절차를 스크립트 하나로 묶어서 명령 한 번에 끝나게 할 생각입니다.

자주 묻는 질문

백업 복구 테스트를 왜 운영 DB에 하면 안 되나요?

복구 명령은 기존 데이터를 덮어쓰는 옵션과 함께 쓰이는 경우가 많아, 실수 한 번이 운영 데이터 손실로 이어질 수 있습니다. 같은 서버라도 임시 데이터베이스를 새로 만들어 거기에 복원하면, 운영 데이터에 전혀 손대지 않고 백업이 살아나는지 확인할 수 있습니다.

복구 리허설은 얼마나 자주 해야 하나요?

백업 구성이 바뀌었을 때(암호화 방식, 저장소, 스크립트 수정)와 분기에 한 번 정도가 현실적인 기준입니다. 매일 할 필요는 없지만, 한 번도 안 해봤다면 백업이 실제로 복원 가능한지 아무도 모르는 상태입니다.

복원한 DB와 운영 DB의 행 수가 다르면 실패인가요?

아닙니다. 백업은 특정 시점의 스냅샷이므로, 그 이후에 쌓인 데이터만큼 운영 DB가 더 많은 것이 정상입니다. 차이가 백업 시각 이후의 활동량과 맞아떨어지는지 확인하면 오히려 검증이 더 정확해집니다.

#백업#복구#PostgreSQL#Backblaze B2#자가호스팅