백업 복구 리허설: RPO·RTO와 복원 검증
격리된 환경에서 백업을 복원하고 파일 존재 이상의 검증을 수행하는 절차입니다.
백업 파일이 있다는 사실만으로 서비스를 복구할 수 있는 것은 아닙니다. 파일이 읽히는지, 필요한 시점의 데이터인지, 애플리케이션이 그 데이터로 동작하는지 확인해야 합니다. 복구 시험은 운영 데이터를 덮어쓰지 않는 격리된 환경에서 진행합니다.
RPO와 RTO를 다르게 정하기
RPO는 허용할 수 있는 데이터 손실의 시간 범위, RTO는 복구에 허용할 시간을 표현합니다. 예를 들어 가상의 게시판이 한 시간 전 데이터로 돌아갈 수 있는지와 한 시간 안에 서비스를 다시 열어야 하는지는 다른 질문입니다.
백업 주기만 보고 RPO가 달성된다고 가정하지 않습니다. 백업 실패를 늦게 발견하면 실제로 사용할 수 있는 최신 사본이 더 오래됐을 수 있습니다. 탐지와 복원 검증 시간이 RTO에 포함되는지도 명확히 정합니다.
리허설 준비물
| 준비물 | 확인 이유 |
|---|---|
| 백업 생성 시각과 형식 | 필요한 시점·도구로 복구 가능한지 |
| 암호화 키 접근 절차 | 파일이 있어도 해독하지 못하는 상황 방지 |
| 격리된 복구 환경 | 운영 덮어쓰기와 외부 발송 방지 |
| 앱·DB 버전 | 호환성 확인 |
| 검증 시나리오 | 행 수뿐 아니라 업무 동작 확인 |
복구 환경의 이메일, 결제, 웹훅 전송은 테스트용으로 분리합니다. 복구된 개인정보의 접근 범위와 리허설 뒤 폐기 절차도 정해야 합니다.
복원 후 무엇을 확인하나
테이블과 파일의 존재, 행 수, 주요 관계의 정합성을 봅니다. 임의 샘플뿐 아니라 가장 최근에 생성한 테스트용 레코드처럼 시점을 확인할 수 있는 항목도 검증합니다. 로그인, 목록 조회, 읽기 전용 핵심 경로를 실행해 봅니다.
DB 파일을 실행 중인 서비스에서 단순 복사해도 일관성이 보장되는지는 DB와 저장 방식에 따라 다릅니다. 사용하는 제품의 공식 백업·복구 절차를 선택해야 합니다. PostgreSQL의 논리 백업과 물리 백업은 목적과 복원 조건이 다릅니다.
복구 시간을 단계별로 기록하기
백업 찾기, 접근 승인, 환경 준비, 다운로드, 복원, 데이터 검증, 앱 확인에 걸린 시간을 나눠 적습니다. 전체 시간만 기록하면 다음 개선 지점을 찾기 어렵습니다. 실패 원인과 필요한 문서를 고친 뒤 다시 리허설합니다.
실제 복구를 해보지 않았다면 ‘복구 가능’이라는 체크 대신 ‘백업 생성 확인, 복구 미검증’으로 표시하는 것이 정확합니다. 배포 변경 기록에도 이 상태를 연결합니다.
SQLite 백업을 실제로 복원해 보는 최소 실습
다음 코드는 Python 표준 라이브러리만 사용합니다. 임시 폴더에서 원본·백업·복원 DB를 새로 만들고 블록이 끝나면 실습 파일을 정리합니다. 사용자의 기존 DB를 찾거나 복사하지 않습니다.
import sqlite3
from pathlib import Path
from tempfile import TemporaryDirectory
with TemporaryDirectory() as folder:
root = Path(folder)
source = sqlite3.connect(root / "source.db")
backup = sqlite3.connect(root / "backup.db")
restored = sqlite3.connect(root / "restored.db")
try:
source.execute("CREATE TABLE notes (id INTEGER PRIMARY KEY, body TEXT NOT NULL)")
source.execute("INSERT INTO notes VALUES (?, ?)", (1, "복원 확인용 데이터"))
source.commit()
source.backup(backup)
source.execute("INSERT INTO notes VALUES (?, ?)", (2, "백업 후 추가"))
source.commit()
backup.backup(restored)
assert restored.execute("PRAGMA integrity_check").fetchone()[0] == "ok"
rows = restored.execute("SELECT id, body FROM notes ORDER BY id").fetchall()
assert rows == [(1, "복원 확인용 데이터")]
assert source.execute("SELECT COUNT(*) FROM notes").fetchone()[0] == 2
print(rows)
finally:
restored.close()
backup.close()
source.close()
복원 결과에는 첫 번째 행만 있습니다. 백업 이후 추가된 두 번째 행이 없다는 사실은 복원 실패가 아니라 복구 시점의 차이입니다. 이 차이를 설명할 수 있어야 RPO를 검증할 수 있습니다. 무결성 검사가 ok여도 업무적으로 필요한 행이 빠졌을 수 있으므로 기대 데이터를 직접 비교합니다.
이 실습은 작은 SQLite DB의 백업 API 동작을 확인합니다. 암호화 키 복구, 대용량 복원 시간, 외부 파일과 DB 사이의 일관성, 지속적인 동시 쓰기까지 검증한 것은 아닙니다. 운영에서는 해당 조건을 별도로 시험하고 실제 걸린 시간을 기록합니다.