LLM WikiAccess-protected knowledge portal
← 스터디 홈
4편 · 약 24분

복구 전략: 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시간이다.

시간 축으로 보는 RTO / RPO
정상 운영
장애
복구 중 (최대 RTO)
RPO: 마지막 복구 가능 시점 ~ 장애 발생 → RTO: 장애 발생 ~ 서비스 정상화 →
RPO를 줄이려면 binlog/WAL을 더 자주 백업
(5분 → 1분 → 실시간 스트리밍)
비용·네트워크 부담 증가
RTO를 줄이려면 물리적 백업 도입
복구 환경 사전 준비
자동화된 복구 파이프라인
주의 RPO 0은 동기 복제 의미
RTO 0은 사실상 불가능
→ 대신 HA failover로 접근
RTO/RPO 관계와 복구 시나리오

RPO·RTO의 Tier별 예시

서비스 유형예시 RPO예시 RTO기술 선택
결제·금융 Tier-1수초 이하30분 이내동기 복제 + 자동 failover
핵심 OLTP Tier-25분2시간비동기 복제 + PITR + 반자동 failover
분석 DB Tier-31시간4시간일별 전체 백업 + 수동 복구
개발/스테이징하루다음 영업일주 1회 백업

실무 함정: RPO와 RTO를 정할 때 인프라팀 혼자 결정하면 안 된다. 비즈니스가 실제로 감수할 수 있는 숫자와, 인프라가 기술적으로 달성할 수 있는 숫자를 함께 논의해야 한다.


PITR: 특정 시점으로의 복구

Point-in-Time Recovery는 데이터베이스를 임의의 과거 시점 상태로 복구하는 능력이다. "어제 오후 2시 33분 58초 직전"처럼 초 단위 정밀도가 가능하다.

PITR은 두 가지 요소로 구성된다.

  1. 베이스 백업: 특정 시점의 데이터파일 스냅샷 (full 또는 physical backup)
  2. 연속 로그 스트림: 베이스 백업 이후 발생한 모든 변경 기록 (MySQL binlog, PostgreSQL WAL)
프로덕션 DB 일요일 02:00
Full backup

월~토 매일
Incremental

상시 binlog/WAL
실시간 스트리밍
백업 저장소 (S3) full-2026-06-22.tar.zst incr-2026-06-23.tar.zst incr-2026-06-24.tar.zst binlog.000401 ~ 000589
(WAL 0/A000000 ~ 0/F800000)
복구 요청 목표: 2026-06-25
14:33:00 직전
1. Full restore
2. Incremental 순서 적용
3. binlog/WAL replay
→ 14:33:00 에서 정지
4. 복구 DB 검증
복구 완료
PITR 복구 흐름

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가 뒤로 돌아가는 경우가 있다.

복구 후 검증 체크리스트

1. 무결성 검사 MySQL: mysqlcheck -A
PostgreSQL: pg_dump | wc
또는 pgBackRest verify
InnoDB 페이지 checksum 확인
2. 데이터 정합성 핵심 테이블 row count
최신 레코드 타임스탬프
합산 값 비교 (SUM, COUNT)
목표 시점과 오차 범위 확인
3. 시퀀스 확인 AUTO_INCREMENT 최대값
PostgreSQL SEQUENCE 현재값
복구 시점 이전 값보다
높아야 함 (중복 방지)
4. 앱 smoke test 읽기 쿼리 1개 이상
쓰기 쿼리 1개 이상
트랜잭션 롤백 확인
연결 풀 정상 확인
모든 단계 통과 후 → read-only로 트래픽 붙이기 → 이상 없으면 read-write 오픈
복구 후 검증 4단계

자주 놓치는 항목

시퀀스/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를 초기화해야 한다.


복구 전략 선택 기준

실수로 DELETE/DROP PITR 사용
실수 직전 시점으로 복구
복구 인스턴스에서 데이터 추출
프로덕션에 선택적 INSERT
서버/디스크 완전 장애 Full + Incremental 복구
가장 최신 백업으로 복구
binlog/WAL로 gap 채우기
replica 없으면 RTO 길어짐
데이터 논리적 손상 PITR + 영향도 분석
손상 시점 파악이 먼저
손상 범위 테이블 특정
피해 테이블만 선택 복구
노드 장애 (replica 있음) HA failover 우선
백업 복구는 마지막 수단
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