FDE 캡스톤: 창고가 같은 주문을 세 번 받았다 · 백업과 롤백 리허설 · 실습
롤백 계획서의 복구 명령은 한 번도 실행된 적이 없었다
목표
WAL 모드 SQLite 를 쓰는 고객 서비스에서 백업 → 증거 → 마이그레이션 → 실패 주입 → 복원 → 백업 시점과 같음 증명을 실제로 돌리고, 그 절차를 migrate.sh·restore.sh·rehearse.sh 로 굳힌다.
왜 중요한가
롤백 계획서의 복구 명령은 대개 실행해 본 적이 없다. 서비스가 떠 있는 채로 cp 한 백업은 integrity_check 가 ok 인데 커밋이 빠져 있고, 강제 종료 뒤 남은 -wal 은 복원한 파일 위에 다시 적용된다. 리허설로 확인하고 숫자로 남기지 않으면, 롤백이 필요한 날 처음 알게 된다.
재료: /opt/lab/p1a-rehearsal/ — liveapp.py(주문 서비스 흉내: start·status·stop·crash), migrations/002_add_currency.sql, migrations/003_customer_unique.sql.
작업 디렉터리는 /root/rehearsal 이다(서비스 DB 는 /root/rehearsal/live.db). 증거 파일은 /root/rehearsal/evidence 에 둔다. 예상 60분이고, 세션이 끝나면 /root/rehearsal 은 사라진다.
단계
1. liveapp.py start 로 서비스를 띄우고, 서비스가 떠 있는 채로 live.db 본파일만 /root/rehearsal/copies/cp-only.db 로 cp 한 뒤, /root/rehearsal/evidence/cp.tsv 에 live 와 cp-only 의 행 수·integrity_check 를 적는다.
2. 서비스가 떠 있는 채로 .backup 으로 /root/rehearsal/backups/pre-v2.db, VACUUM INTO 로 /root/rehearsal/backups/pre-v2-vacuum.db 를 만들고 evidence/backup.tsv 에 file<TAB>rows<TAB>integrity<TAB>journal_mode 로 적는다.
3. 두 백업의 파일 sha256 과 .sha3sum --schema, 행 수·user_version·integrity·시각을 evidence/baseline.json 에 남긴다.
4. migrate.sh DB NNN_이름.sql 을 /root/rehearsal/migrate.sh 로 만들고 live.db 에 002 를 적용한다. user_version 이 NNN-1 일 때만 적용하고, 이미 NNN 이면 아무것도 하지 않고 0, 그 밖이면 거절한다.
5. live.db 에 003 을 적용해 실패시키고 evidence/failure.json 에 종료 코드·user_version·내용 해시 전후·오류를 남긴다. migrate.sh 는 실패하면 흔적이 없어야 한다.
6. restore.sh BACKUP TARGET 을 /root/rehearsal/restore.sh 로 만든다. 백업이 온전하지 않으면 대상에 손대지 않고 거절하고, 남은 -wal 이 있어도 백업과 같은 내용으로 복원해 증명한다.
7. liveapp.py crash 로 배포 중 강제 종료를 일으킨 뒤 live.db 를 pre-v2.db 로 복원하고 evidence/restore.json 을 남긴다.
8. rehearse.sh DB WORKDIR UP.sql BROKEN.sql 을 /root/rehearsal/rehearse.sh 로 만든다. 백업부터 복원 증명까지 한 번에 돌고 WORKDIR/report.json 을 남기며, 무엇 하나 증명하지 못하면 0 이 아닌 값으로 끝난다.
참고
- 서비스가 떠 있는 동안에는 여러분의 sqlite3 연결이 마지막 연결이 아니어서 -wal 이 유지된다. 서비스를 멈춘 채 live.db 를 열었다 닫으면 체크포인트가 돌아 1단계 조건이 사라진다(
liveapp.py start를 다시 해도 이미 옮겨진 뒤다). 1·2단계는 서비스를 띄운 채로 한다. - 백업 파일은 읽기만 한다:
sqlite3 'file:backups/pre-v2.db?immutable=1' 'PRAGMA integrity_check' - 파일 머리 18번째 바이트(0부터 셈)는 1 이면 롤백 저널, 2 면 WAL 이다:
od -An -tu1 -j18 -N1 파일 - 채점기는 여러분이 띄운 서비스에 기대지 않는다. 4·6·8단계는 새 DB 를 만들어 스크립트를 직접 돌리고, 나머지는 증거 파일을 백업 파일과 liveapp 의 기록(app/events.log)으로 다시 계산해 대조한다. 백업 파일을 지우거나 고치면 앞 단계가 다시 떨어진다.
- 흔한 실수:
sqlite3 db < file.sql로 마이그레이션,-bail없는 BEGIN/COMMIT, 남은 -wal 을 두고 cp 로 복원.
단계 8개
- 떠 있는 서비스의 DB 를 cp 하면 무엇을 잃나
- .backup 과 VACUUM INTO 로 온라인 백업
- 복원 뒤 비교할 기준값 남기기
- 판 번호를 지키는 migrate.sh
- 실패한 마이그레이션은 흔적이 없어야 한다
- 남은 -wal 까지 처리하는 restore.sh
- 배포 중 강제 종료된 운영 DB 되돌리기
- 한 번에 도는 리허설과 보고서