- backup-db.sh: pg_dump | gzip | S3 업로드 + 로컬 7일 보관, 덤프 크기 검증 - restore-db.sh: 목록 조회·복원(확인 프롬프트·복원 전 자동 스냅샷) - systemd timer로 매일 03:00 KST 실행 (AL2023에 cron 없음, 이력이 journalctl에 남음) - S3 버킷: 퍼블릭 차단 + AES256 암호화 + 30일 수명주기 - IAM 역할(mirim-backup-role): 해당 버킷 postgres/ 에 PutObject만 — 최소 권한, 액세스 키 없음 - BACKUP.md: 구성·설계 이유·복원 리허설 절차 검증: 백업 성공(8.5MB→102KB), S3 업로드 확인, 임시 DB 복원 후 운영본과 건수 완전 일치 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3.4 KiB
3.4 KiB
DB 백업·복원 운영 가이드
학습 플랫폼 DB(PostgreSQL)는 매일 새벽 3시(KST) 자동 백업되어 S3에 올라갑니다.
구성
EC2 (3.36.160.246)
├── mirim-postgres 컨테이너 ← 원본 DB
├── ~/backups/*.sql.gz ← 서버 로컬 사본 (7일 보관)
└── systemd timer (매일 03:00 KST)
↓ pg_dump | gzip | aws s3 cp
S3: awesomedev-mirim-backup-apne2/postgres/ ← 30일 보관 후 자동 삭제
3-2-1 원칙: 3벌(원본·로컬·S3) / 2종 매체(EC2 디스크·S3) / 1벌 원격(S3)
왜 이렇게 만들었나
| 결정 | 이유 |
|---|---|
| RDS 대신 컨테이너 + 백업 | 학생 5명 규모에 RDS는 월 $15~25가 계속 나감. 백업만 있으면 실질 위험은 같이 사라진다. 게다가 이 compose 파일이 "도커 입문" 코스의 교재다 |
| 액세스 키 대신 IAM 역할 | 키를 파일에 적으면 유출 시 끝. 역할(mirim-backup-role)은 이 서버에서만, 이 버킷의 postgres/ 에만 쓸 수 있다 (최소 권한) |
| cron 대신 systemd timer | Amazon Linux 2023에 cron이 없기도 하고, 타이머는 실행 이력이 journalctl에 남고 Persistent=true로 서버가 꺼졌던 시간대 작업도 따라잡는다 |
| SQL 덤프(pg_dump) | 파일 복사가 아니라 "다시 만드는 SQL"이라 PG 버전이 달라도 복원되고, 텍스트라 gzip이 잘 먹는다 (8.5MB → 102KB) |
자주 쓰는 명령
# 백업 목록 보기
~/mirim-app/deploy/restore-db.sh
# 지금 즉시 백업
sudo systemctl start mirim-backup.service
# 백업 이력·로그 확인
sudo journalctl -u mirim-backup.service -n 30
# 다음 실행 예정 시각
systemctl list-timers mirim-backup.timer
# 복원 (확인 프롬프트 있음. 복원 전 현재 상태를 자동으로 먼저 백업함)
~/mirim-app/deploy/restore-db.sh mirim-2026-07-17_030000.sql.gz
docker compose -f docker-compose.prod.yml restart backend # 복원 후 재시작
복원 리허설 (권장: 분기 1회)
복원해 본 적 없는 백업은 백업이 아니다.
운영 DB를 건드리지 않고 임시 DB에 복원해 검증하는 방법:
F=$(ls -t ~/backups/mirim-*.sql.gz | head -1)
docker exec mirim-postgres psql -U mirim -d postgres -c 'CREATE DATABASE restore_test;'
gunzip -c $F | docker exec -i mirim-postgres psql -U mirim -d restore_test
# 건수 대조
docker exec mirim-postgres psql -U mirim -d restore_test -c \
"SELECT count(*) FROM users; SELECT count(*) FROM quiz_question;"
docker exec mirim-postgres psql -U mirim -d postgres -c 'DROP DATABASE restore_test;'
최초 검증 결과 (2026-07-17): 복원본과 운영 원본이 완전 일치 — 사용자 5 / 퀴즈문항 355 / 문서 17 / 체크리스트 46 / 코딩문제 20 ✅
무엇이 백업되고, 무엇이 안 되나
| 대상 | 백업 | 비고 |
|---|---|---|
| 학생 진도·제출물·퀴즈 성적·코딩 제출 | ✅ | 이게 진짜 자산 — 코드에 없는 유일한 데이터 |
| 사용자 계정(비밀번호 해시 포함) | ✅ | |
| 코스·문서·퀴즈 문항·체크리스트 | ✅ | (없어도 시드로 재생성 가능) |
| Gitea 저장소 | ❌ | 별도 볼륨(gitea-data). 필요하면 백업 추가 검토 |
| 소스 코드 | ❌ | Gitea + 로컬에 있음 |
비용
- S3 저장: 102KB × 30일 ≈ 3MB → 사실상 $0 (월 몇 원)
- 데이터 전송: 업로드는 무료
- 총 추가 비용 ≈ $0