복구 전략: RTO/RPO, PITR, 복구 리허설, 복구 후 검증
백업이 있다고 복구할 수 있는 것은 아니다
장애가 터진 새벽 2시, 팀 채널에 "백업 있죠?"라는 메시지가 뜬다. 있다고 답하는 순간, 이어지는 질문은 "언제까지 복구 가능해요?"다. 이것이 RTO(Recovery Time Objective)다. "어디까지 데이터가 살아요?"는 RPO(Recovery Point Objective)다.
두 숫자는 서비스와 비즈니스가 허용하는 한계를 나타낸다. 장애가 나기 전에 합의해 두지 않으면, 장애 중에 누군가 어림잡아 답하게 된다. 그것은 보통 틀린 답이다.
이 장에서는 RTO/RPO의 실무 의미, PITR의 동작 원리와 MySQL/PostgreSQL 구현, 복구 리허설의 절차와 설계, 복구 후 검증까지 다룬다.
RTO와 RPO: 정의와 실무적 차이
RPO(Recovery Point Objective): 장애 발생 시 허용 가능한 최대 데이터 손실 시간. "우리는 최대 5분치 데이터를 잃을 수 있다"는 RPO 5분이다.
RTO(Recovery Time Objective): 장애 발생 후 서비스를 복구하는 데 허용되는 최대 시간. "두 시간 안에 서비스를 정상화해야 한다"는 RTO 2시간이다.
(5분 → 1분 → 실시간 스트리밍)
비용·네트워크 부담 증가
복구 환경 사전 준비
자동화된 복구 파이프라인
RTO 0은 사실상 불가능
→ 대신 HA failover로 접근
RPO·RTO의 Tier별 예시
| 서비스 유형 | 예시 RPO | 예시 RTO | 기술 선택 |
|---|---|---|---|
| 결제·금융 Tier-1 | 수초 이하 | 30분 이내 | 동기 복제 + 자동 failover |
| 핵심 OLTP Tier-2 | 5분 | 2시간 | 비동기 복제 + PITR + 반자동 failover |
| 분석 DB Tier-3 | 1시간 | 4시간 | 일별 전체 백업 + 수동 복구 |
| 개발/스테이징 | 하루 | 다음 영업일 | 주 1회 백업 |
실무 함정: RPO와 RTO를 정할 때 인프라팀 혼자 결정하면 안 된다. 비즈니스가 실제로 감수할 수 있는 숫자와, 인프라가 기술적으로 달성할 수 있는 숫자를 함께 논의해야 한다.
PITR: 특정 시점으로의 복구
Point-in-Time Recovery는 데이터베이스를 임의의 과거 시점 상태로 복구하는 능력이다. "어제 오후 2시 33분 58초 직전"처럼 초 단위 정밀도가 가능하다.
PITR은 두 가지 요소로 구성된다.
- 베이스 백업: 특정 시점의 데이터파일 스냅샷 (full 또는 physical backup)
- 연속 로그 스트림: 베이스 백업 이후 발생한 모든 변경 기록 (MySQL binlog, PostgreSQL WAL)
Full backup
↓
월~토 매일
Incremental
↓
상시 binlog/WAL
실시간 스트리밍
(WAL 0/A000000 ~ 0/F800000)
14:33:00 직전 1. Full restore
2. Incremental 순서 적용
3. binlog/WAL replay
→ 14:33:00 에서 정지
4. 복구 DB 검증 복구 완료
MySQL PITR 실행
# 1. XtraBackup full restore
xtrabackup --prepare --target-dir=/restore/full
xtrabackup --prepare --incremental-dir=/restore/incr \
--target-dir=/restore/full
xtrabackup --copy-back --target-dir=/restore/full
# 2. binlog 목표 시점까지 replay
# xtrabackup_binlog_info 파일에서 복구 시작 포지션 확인
cat /var/lib/mysql/xtrabackup_binlog_info
# mysql-bin.000401 18294761
mysqlbinlog --start-position=18294761 \
--stop-datetime="2026-06-25 14:33:00" \
/backup/binlog/mysql-bin.000401 \
/backup/binlog/mysql-bin.000402 \
... | mysql -u root -p핵심 전제: log_bin = ON, binlog_expire_logs_seconds가 RPO보다 충분히 길어야 한다. 백업 완료 시점 이후의 binlog가 저장소에 있어야 chain이 완성된다.
PostgreSQL PITR 실행
# 1. pgBackRest restore (target time 지정)
pgbackrest --stanza=main \
--type=time \
"--target=2026-06-25 14:33:00+09" \
--target-action=promote \
restore
# 2. postgresql.conf / recovery.signal
# pgBackRest가 자동으로 recovery.signal을 생성하고
# restore_command를 설정함
# PostgreSQL 기동 후 WAL replay 자동 실행
pg_ctl start -D /var/lib/pgsql/data
# 3. 목표 시점에 도달하면 promote
# (--target-action=promote 지정 시 자동 처리)핵심 전제: wal_level = replica, archive_mode = on, archive_command가 실제로 WAL을 저장소로 보내고 있어야 한다.
복구 리허설: 미리 해보지 않으면 장애 중에 못 한다
복구 절차서가 있는 것과 실제로 복구해본 것은 다르다. 복구 리허설은 장애가 없는 상황에서 의도적으로 복구를 실행해 시간과 절차를 검증하는 훈련이다.
리허설 설계 원칙
1. 격리 환경 사용: 절대 프로덕션에 restore하지 않는다. staging 인스턴스, 임시 클라우드 인스턴스, 또는 테스트 클러스터를 쓴다.
2. 랜덤 시점 선택: 항상 "어제 새벽 2시 full backup"만 복구하면 binlog replay path가 검증되지 않는다. 랜덤하게 "4일 전 오후 3시 15분"처럼 arbitrary한 시점을 선택해 전체 체인을 검증한다.
3. 시간을 측정한다: 리허설의 핵심 산출물은 실제 소요 시간이다. 단계별로 기록한다.
복구 리허설 시간 측정 예시
──────────────────────────────────────
[T+00:00] 백업 파일 다운로드 시작
[T+08:12] 다운로드 완료 (300 GB, 약 8분)
[T+08:12] XtraBackup prepare 시작
[T+22:40] prepare 완료 (약 14분)
[T+22:40] copy-back 시작
[T+35:20] copy-back 완료 (약 13분)
[T+35:20] binlog replay 시작
[T+47:10] 목표 시점 도달 (약 12분)
[T+47:10] 검증 쿼리 실행
[T+50:00] 검증 완료
──────────────────────────────────────
총 실제 RTO: 50분
SLA RTO: 60분 → ✓ 마진 10분주의: 마진이 10분이면 이미 위험하다. 장애 상황에서는 비정상적인 binlog 파일이나 네트워크 불안정으로 추가 시간이 필요할 수 있다. RTO의 60~70%에서 완료될 수 있어야 안전하다.
4. 자동화된 정기 리허설: 매주 자동으로 staging에 restore하고 row count를 검증하는 파이프라인을 만든다.
#!/bin/bash
# weekly-restore-test.sh (CronJob 또는 CI pipeline)
set -e
# 최신 full backup restore
pgbackrest --stanza=main --type=immediate restore
pg_ctl start -D $PGDATA
# 핵심 테이블 row count 검증
EXPECTED=1823941
ACTUAL=$(psql -t -c "SELECT COUNT(*) FROM orders WHERE created_at > NOW()-INTERVAL '7 days'")
if [ "$ACTUAL" -lt "$EXPECTED" ]; then
echo "RESTORE_VERIFICATION_FAILED: expected>=$EXPECTED got $ACTUAL"
exit 1
fi
echo "RESTORE_VERIFICATION_OK at $(date)"
pg_ctl stop -D $PGDATA리허설 주기 권고
| Tier | 자동화 복구 검증 | 전체 PITR 리허설 | 담당자 참여 드릴 |
|---|---|---|---|
| Tier-1 | 매주 | 월 1회 | 분기 1회 |
| Tier-2 | 격주 | 분기 1회 | 반기 1회 |
| Tier-3 | 월 1회 | 반기 1회 | 연 1회 |
복구 후 검증: 복구가 끝나도 서비스로 가면 안 된다
DB가 복구됐다고 바로 트래픽을 붙이면 안 된다. 복구 과정에서 일부 데이터가 누락되거나, 인덱스가 corrupt됐거나, 시퀀스/AUTO_INCREMENT가 뒤로 돌아가는 경우가 있다.
복구 후 검증 체크리스트
mysqlcheck -APostgreSQL:
pg_dump | wc또는
pgBackRest verifyInnoDB 페이지 checksum 확인
최신 레코드 타임스탬프
합산 값 비교 (SUM, COUNT)
목표 시점과 오차 범위 확인
PostgreSQL SEQUENCE 현재값
복구 시점 이전 값보다
높아야 함 (중복 방지)
쓰기 쿼리 1개 이상
트랜잭션 롤백 확인
연결 풀 정상 확인
자주 놓치는 항목
시퀀스/AUTO_INCREMENT 확인
-- MySQL: 복구 후 AUTO_INCREMENT 확인
SELECT TABLE_NAME, AUTO_INCREMENT
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = 'your_db'
ORDER BY AUTO_INCREMENT DESC
LIMIT 10;
-- 실제 최대 ID와 비교
SELECT MAX(id) FROM orders;
-- AUTO_INCREMENT가 MAX(id)보다 낮으면 → 중복 키 오류 발생 가능
-- 조정: ALTER TABLE orders AUTO_INCREMENT = <max+1>;-- PostgreSQL: SEQUENCE 현재값 확인
SELECT sequencename, last_value
FROM pg_sequences
WHERE schemaname = 'public';
-- 실제 max와 비교
SELECT MAX(id) FROM orders;
-- SEQUENCE가 뒤로 돌아가있으면:
-- SELECT setval('orders_id_seq', (SELECT MAX(id) FROM orders));복제 재연결 (복구 DB를 replica로 쓸 경우)
복구된 DB를 replica로 replication topology에 넣기 전에 반드시 position/GTID가 맞는지 확인하고 binlog relay를 초기화해야 한다.
복구 전략 선택 기준
실수 직전 시점으로 복구
복구 인스턴스에서 데이터 추출
프로덕션에 선택적 INSERT
가장 최신 백업으로 복구
binlog/WAL로 gap 채우기
replica 없으면 RTO 길어짐
손상 시점 파악이 먼저
손상 범위 테이블 특정
피해 테이블만 선택 복구
백업 복구는 마지막 수단
RTO는 초~분 단위
(다음 장 참조)
HA가 있으면 노드 장애는 failover로 해결하고 백업 복구는 논리적 손상이나 재해 수준 장애에만 쓴다. 백업 복구는 RTO가 길기 때문에 HA와 상호보완적으로 설계해야 한다.
운영 체크리스트
복구 능력 확인
- [ ] RPO와 RTO가 비즈니스와 합의된 숫자인가?
- [ ] 현재 백업 구성으로 RPO를 달성할 수 있는가?
- [ ] 최근 리허설에서 측정한 실제 복구 시간이 RTO 70% 이내인가?
- [ ] PITR에 필요한 binlog/WAL이 RPO 기간만큼 저장소에 보관되고 있는가?
- [ ] 베이스 백업 → 로그 스트림 chain이 끊김 없이 유지되는가?
복구 절차 준비
- [ ] 복구 절차서가 문서화되어 있고 최근 6개월 이내에 검증됐는가?
- [ ] 복구에 필요한 환경(인스턴스, 권한, 네트워크)이 사전에 준비되어 있는가?
- [ ] 복구 후 검증 쿼리가 정의되어 있는가?
- [ ] 복구 완료 판단 기준이 명확한가?
기억할 문장
RTO/RPO는 목표값이고, 리허설에서 측정한 실제 복구 시간이 그 목표를 실제로 달성할 수 있는지를 증명한다. 문서에 적힌 절차가 아니라 직접 실행해서 측정한 시간이 실제 RTO다.
다음 장에서는 백업 복구보다 훨씬 빠른 복구 경로인 고가용성 패턴, primary-replica 아키텍처, 자동 failover, quorum, split-brain 방지를 다룬다.
References
- PostgreSQL Documentation, Continuous Archiving and PITR — https://www.postgresql.org/docs/current/continuous-archiving.html
- pgBackRest User Guide, Recovery — https://pgbackrest.org/user-guide.html#recovery
- Percona XtraBackup, Point-in-Time Recovery — https://docs.percona.com/percona-xtrabackup/8.0/point-in-time-recovery.html
- Google Cloud, Perform point-in-time recovery (Cloud SQL MySQL) — https://docs.cloud.google.com/sql/docs/mysql/backup-recovery/pitr
- Google Cloud, Perform point-in-time recovery (Cloud SQL PostgreSQL) — https://docs.cloud.google.com/sql/docs/postgres/backup-recovery/pitr
- Stormatics, Understanding Disaster Recovery in PostgreSQL — https://stormatics.tech/blogs/understanding-disaster-recovery-in-postgresql
- JusDB Blog, PostgreSQL Point-in-Time Recovery (PITR): Complete Guide — https://www.jusdb.com/blog/postgresql-point-in-time-recovery-pitr
- AWS Well-Architected Framework, Disaster Recovery Options — https://docs.aws.amazon.com/wellarchitected/latest/reliability-pillar/disaster-recovery-dr-objectives.html
- Kunal Ganglani, Backup & recovery: point-in-time restore, snapshots & disaster recovery — https://www.kunalganglani.com/learning-paths/sre/sre-db-backup