Claude Code로 개발하다가 실수로 프로덕션 DB를 날려먹었습니다. DigitalOcean 자동 백업으로 복구한 과정과 삽질을 공유합니다.
사고 경위 😱
Claude Code로 DB 마이그레이션 작업을 하고 있었습니다. 로컬에서 테스트하려던 건데…
# 이러려고 했던 것
mysql -h localhost ... -e "DROP DATABASE test_db;"
# 실제로 실행된 것
mysql -h my-db-cluster-do-user-xxxxx-0.g.db.ondigitalocean.com ... -e "DROP DATABASE my_app_db;"환경 변수가 프로덕션으로 되어 있었고, Claude가 생성한 명령어를 확인 없이 실행했습니다 ㅎ…
데이터가 전부 날아갔습니다.
다행히 DigitalOcean 관리형 데이터베이스는 매일 자동 백업을 제공합니다. 하지만 여기서부터가 진짜 삽질의 시작이었습니다.
문제 상황
DigitalOcean의 백업 복구 방법은 fork (새 DB 클러스터 생성)뿐입니다:
- ❌ 기존 DB에 직접 복원 불가
- ❌ Fork하면 접속 정보(호스트, 포트, 비밀번호) 전부 변경
- ❌ 환경 변수 수정하고 재배포 필요
- ❌ 다운타임 발생
급한 상황에서 재배포는 너무 오래 걸립니다. 접속 정보를 그대로 유지하면서 백업만 복구하는 방법을 찾아야 했습니다.
복구 전략
Fork로 임시 DB 생성 → 데이터 덤프 → 기존 DB에 복원 → 임시 DB 삭제
- Fork로 백업에서 임시 DB 생성
- 임시 DB에서 데이터 덤프
- 기존 DB에 데이터 복원 (접속 정보 그대로)
- 임시 DB 삭제
전제 조건
# doctl 설치 확인
doctl version
# 인증 (처음이라면)
doctl auth init
# API 토큰 입력: https://cloud.digitalocean.com/account/api/tokens
# MySQL 클라이언트 설치 확인
mysql --versionStep 1: 백업 확인
먼저 침착하게 백업이 있는지 확인합니다.
데이터베이스 목록 조회
doctl databases list출력:
ID Name Engine Version Nodes Region Status
f81d4fae-7dec-11d0-a765-00a0c91e6bf6 my-db-prod mysql 8 1 sgp1 online백업 목록 확인
doctl databases backups f81d4fae-7dec-11d0-a765-00a0c91e6bf6출력:
Size (GB) Created At
0.29 2026-01-01 10:58:09 UTC
0.29 2026-01-02 10:58:09 UTC
0.29 2026-01-03 10:58:10 UTC
0.29 2026-01-04 10:58:09 UTC
0.29 2026-01-05 10:58:09 UTC
0.29 2026-01-06 10:58:10 UTC
0.29 2026-01-07 10:58:08 UTC
0.29 2026-01-08 10:58:08 UTC ← 최신 (데이터 날리기 직전)휴… 다행히 백업이 있습니다. 최신 백업으로 복구하면 됩니다.
현재 접속 정보 확인
나중에 복원할 때 필요하니 메모해둡니다:
doctl databases connection f81d4fae-7dec-11d0-a765-00a0c91e6bf6출력:
URI: mysql://doadmin:your_password_here@my-db-prod-do-user-xxxxx-0.g.db.ondigitalocean.com:25060/defaultdb?ssl-mode=REQUIRED
Host: my-db-prod-do-user-xxxxx-0.g.db.ondigitalocean.com
Port: 25060
User: doadmin
Password: your_password_hereStep 2: Fork로 임시 DB 생성
백업에서 임시 데이터베이스를 생성합니다.
doctl databases fork my-db-temp \
--restore-from-cluster-id f81d4fae-7dec-11d0-a765-00a0c91e6bf6 \
--restore-from-timestamp "2026-01-08 10:58:08 +0000 UTC" \
--wait출력:
Notice: Database forking is in progress, waiting for database to be online
........................
Notice: Database created
ID: a1b2c3d4-5678-90ab-cdef-1234567890ab약 5-10분 소요됩니다. --wait 옵션으로 완료될 때까지 기다립니다.
임시 DB 접속 정보 확인
doctl databases connection a1b2c3d4-5678-90ab-cdef-1234567890ab출력:
Host: my-db-temp-do-user-xxxxx-0.i.db.ondigitalocean.com
Port: 25060
Password: your_password_here호스트가 달라졌습니다 (.g.db. → .i.db.).
Step 3: 데이터 덤프 - 첫 번째 삽질 🚨
삽질 #1: defaultdb만 덤프해서 거의 빈 파일
처음에는 당연히 defaultdb를 덤프했습니다:
mysqldump \
-h my-db-temp-do-user-xxxxx-0.i.db.ondigitalocean.com \
-P 25060 \
-u doadmin \
-p'your_password_here' \
--ssl-mode=REQUIRED \
defaultdb > backup.sqlls -lh backup.sql
# -rw-r--r-- 1.6K backup.sql어라? 1.6KB??
내 DB가 몇백 MB는 되는데… 파일을 열어봤습니다:
head backup.sql-- MySQL dump 10.13
-- Host: chapssals-dev-temp...
-- GTID state at the beginning...
-- Dump completed on 2026-01-08 23:59:21테이블이 하나도 없습니다 😱
패닉…
원인 파악: defaultdb는 시스템 DB
침착하게 데이터베이스 목록을 확인했습니다:
mysql -h my-db-temp-do-user-xxxxx-0.i.db.ondigitalocean.com \
-P 25060 -u doadmin -p'your_password_here' \
--ssl-mode=REQUIRED \
-e "SHOW DATABASES;"출력:
Database
my_app_db ← 앗! 실제 DB
my_service_db ← 이것도!
defaultdb ← 시스템 기본 DB (비어있음)
information_schema
mysql
performance_schema
sys아… defaultdb는 DigitalOcean의 시스템 기본 DB였고, 실제 애플리케이션 데이터는 my_app_db와 my_service_db에 있었습니다.
처음 DB 만들 때 따로 DB 이름을 지정했던 게 기억났습니다.
제대로 덤프하기
mysqldump \
-h my-db-temp-do-user-xxxxx-0.i.db.ondigitalocean.com \
-P 25060 \
-u doadmin \
-p'your_password_here' \
--ssl-mode=REQUIRED \
--databases my_app_db my_service_db \
> full_backup.sqlls -lh full_backup.sql
# -rw-r--r-- 172K full_backup.sql172KB! 이제 제대로 됐습니다.
Step 4: 복원 - 두 번째 삽질 🚨
삽질 #2: GTID 권한 오류
기존 DB에 복원을 시도했습니다:
mysql -h my-db-prod-do-user-xxxxx-0.g.db.ondigitalocean.com \
-P 25060 -u doadmin -p'your_password_here' \
--ssl-mode=REQUIRED \
< full_backup.sql에러 발생:
mysql: [Warning] Using a password on the command line interface can be insecure.
ERROR 1227 (42000) at line 18: Access denied;
you need (at least one of) the SUPER, SYSTEM_VARIABLES_ADMIN
or SESSION_VARIABLES_ADMIN privilege(s) for this operation또 막혔습니다.
원인: GTID 설정 때문
덤프 파일을 열어보니:
head -30 full_backup.sql...
SET @@GLOBAL.GTID_PURGED=/*!80000 '+'*/
'6c7141d0-eca2-11f0-a719-7221ea71c96e:1-15,
af270e6b-4052-11f0-af1e-eabcad7bafdc:1-1647,
fe1b83c8-c598-11f0-80a2-2eda5aa0e40a:1-486';
...GTID(Global Transaction Identifier) 설정이 있습니다. 이걸 적용하려면 SUPER 권한이 필요한데, DigitalOcean 관리형 DB에서는 이 권한을 주지 않습니다.
보안상의 이유로 제한된 권한만 제공합니다.
해결: GTID 없이 다시 덤프
mysqldump \
-h my-db-temp-do-user-xxxxx-0.i.db.ondigitalocean.com \
-P 25060 \
-u doadmin \
-p'your_password_here' \
--ssl-mode=REQUIRED \
--set-gtid-purged=OFF \
--databases my_app_db my_service_db \
> full_backup_no_gtid.sql핵심 옵션: --set-gtid-purged=OFF
이 옵션으로 GTID 정보를 제거합니다.
다시 복원
mysql -h my-db-prod-do-user-xxxxx-0.g.db.ondigitalocean.com \
-P 25060 -u doadmin -p'your_password_here' \
--ssl-mode=REQUIRED \
< full_backup_no_gtid.sqlmysql: [Warning] Using a password on the command line interface can be insecure.경고만 나오고 에러 없이 완료!
복원 확인
mysql -h my-db-prod-do-user-xxxxx-0.g.db.ondigitalocean.com \
-P 25060 -u doadmin -p'your_password_here' \
--ssl-mode=REQUIRED \
-e "SHOW DATABASES;"출력:
Database
my_app_db ← 복원됨!
my_service_db ← 복원됨!
defaultdb
...테이블 확인:
mysql -h my-db-prod-do-user-xxxxx-0.g.db.ondigitalocean.com \
-P 25060 -u doadmin -p'your_password_here' \
--ssl-mode=REQUIRED \
my_app_db -e "SHOW TABLES;"데이터가 돌아왔습니다! 🎉
Step 5: 임시 DB 삭제
비용 절감을 위해 임시 DB를 삭제합니다:
doctl databases delete a1b2c3d4-5678-90ab-cdef-1234567890ab --force임시 DB는 시간당 비용이 청구되므로, 사용 후 바로 삭제하는 게 좋습니다.
복구 완료! 🎉
소요 시간: 약 30분 (삽질 포함)
결과:
- ✅ 기존 접속 정보 그대로 유지
- ✅ 환경 변수 수정 불필요
- ✅ 재배포 불필요
- ✅ 다운타임 없음
- ✅ 백업 데이터 완전 복원
애플리케이션을 다시 시작하니 정상 작동합니다.
삽질 요약 및 교훈
삽질 1: defaultdb만 덤프 (1.6KB)
문제: defaultdb는 시스템 기본 DB로 비어있음
해결: SHOW DATABASES;로 실제 애플리케이션 DB 이름 확인
# ❌ 잘못된 방법
mysqldump ... defaultdb > backup.sql # 1.6KB, 데이터 없음
# ✅ 올바른 방법
mysqldump ... --databases my_app_db my_service_db > backup.sql # 172KB교훈: 덤프 전에 항상 실제 DB 이름을 확인하세요. defaultdb는 함정입니다.
삽질 2: GTID 권한 오류 (ERROR 1227)
문제: ERROR 1227 - SUPER 권한 필요
원인: 관리형 DB는 보안상 SUPER 권한을 제공하지 않음
해결: --set-gtid-purged=OFF 옵션으로 GTID 정보 제거
# ❌ 오류 발생
mysqldump ... --databases my_app_db > backup.sql
# 복원 시 ERROR 1227 발생
# ✅ 정상 작동
mysqldump ... --set-gtid-purged=OFF --databases my_app_db > backup.sql교훈: DigitalOcean 관리형 DB에서는 항상 --set-gtid-purged=OFF를 사용하세요.
삽질 0: 실수로 프로덕션 DB 삭제
문제: Claude Code가 생성한 명령어를 확인 없이 실행
원인:
- 환경 변수가 프로덕션으로 설정됨
- 명령어 확인 없이 바로 실행
.env파일 관리 소홀
해결책:
-
환경 변수 분리
# .env.local (로컬 개발용) DB_HOST=localhost # .env.production (절대 로컬에 두지 않기!) DB_HOST=production-host -
위험한 명령어는 항상 확인
# DROP, DELETE, TRUNCATE 등은 항상 한 번 더 확인 # 특히 -h 옵션으로 호스트 확인 -
프로덕션은 읽기 전용 계정 사용
# 로컬 개발 시에는 읽기 전용 계정으로 접속 DB_USER=readonly_user -
Claude Code 사용 시 주의
- AI가 생성한 명령어는 항상 검토
- 특히 DB 변경 작업은 더욱 신중하게
- 프로덕션 작업 전에는 환경 변수 재확인
교훈: AI가 편리하지만, 최종 책임은 개발자에게 있습니다. 위험한 명령어는 반드시 검토하세요.
전체 명령어 정리
# 1. 백업 확인
doctl databases list
doctl databases backups <db-id>
doctl databases connection <db-id> # 접속 정보 메모
# 2. Fork로 임시 DB 생성
doctl databases fork temp-db \
--restore-from-cluster-id <original-db-id> \
--restore-from-timestamp "2026-01-08 10:58:08 +0000 UTC" \
--wait
# 3. 실제 DB 이름 확인 (중요!)
mysql -h <temp-host> -P <port> -u doadmin -p'<password>' \
--ssl-mode=REQUIRED -e "SHOW DATABASES;"
# 4. 올바른 덤프 (GTID 없이)
mysqldump -h <temp-host> -P <port> -u doadmin -p'<password>' \
--ssl-mode=REQUIRED \
--set-gtid-purged=OFF \
--databases <actual-db-name1> <actual-db-name2> \
> backup.sql
# 5. 파일 크기 확인 (매우 중요!)
ls -lh backup.sql
# 몇 KB밖에 안 나오면 뭔가 잘못된 것
# 6. 기존 DB에 복원
mysql -h <original-host> -P <port> -u doadmin -p'<password>' \
--ssl-mode=REQUIRED < backup.sql
# 7. 복원 확인
mysql -h <original-host> -P <port> -u doadmin -p'<password>' \
--ssl-mode=REQUIRED -e "SHOW DATABASES;"
# 8. 임시 DB 삭제
doctl databases delete <temp-db-id> --force체크리스트
복구 작업 전에 확인하세요:
-
doctl설치 및 인증 완료 - MySQL 클라이언트 설치 확인
- 백업 존재 확인 (
doctl databases backups) - 원본 DB 접속 정보 메모
- 실제 DB 이름 확인 (
SHOW DATABASES;) - 덤프 파일 크기 확인 (적절한 크기인가?)
-
--set-gtid-purged=OFF옵션 사용 - 복원 후 데이터 검증
- 임시 DB 삭제 (비용 절감)
대안: 그냥 Fork 사용하기
만약 접속 정보 변경이 괜찮다면 훨씬 간단합니다:
# 1. 백업에서 새 DB 생성 (5-10분)
doctl databases fork my-new-db \
--restore-from-cluster-id <old-db-id> \
--restore-from-timestamp "2026-01-08 10:58:08 +0000 UTC"
# 2. 환경 변수 업데이트
# .env 파일이나 배포 설정에서 변경
DB_HOST=<new-host>
DB_PORT=<new-port>
DB_PASSWORD=<new-password>
# 3. 애플리케이션 재배포
# 4. 확인 후 기존 DB 삭제
doctl databases delete <old-db-id>장점:
- 덤프/복원 과정 없음
- 5분 만에 완료
- 실수 가능성 낮음
단점:
- 환경 변수 변경 필요
- 재배포 필요
- 다운타임 발생 가능
급하지 않다면 이 방법을 추천합니다.
FAQ
Q: PostgreSQL은 어떻게 하나요?
pg_dump와 psql을 사용합니다:
# 덤프
pg_dump -h <host> -p <port> -U doadmin \
--no-privileges --no-owner \
-d <dbname> > backup.sql
# 복원
psql -h <host> -p <port> -U doadmin -d <dbname> < backup.sqlPostgreSQL도 권한 이슈가 있으니 --no-privileges --no-owner 옵션을 사용하세요.
Q: 백업이 7일 이상 지났는데요?
DigitalOcean 기본 백업은 7일만 보관합니다. 더 긴 보관이 필요하면:
옵션 1: SnapShooter (유료)
- DigitalOcean과 통합
- 커스텀 스케줄, 장기 보관
옵션 2: Cron으로 직접 백업 (무료)
# /etc/cron.d/db-backup
0 3 * * * mysqldump ... > /backups/db_$(date +\%Y\%m\%d).sql
# S3나 다른 스토리지에 업로드
0 4 * * * aws s3 cp /backups/*.sql s3://my-backups/옵션 3: SimpleBackups (유료)
- 자동 백업 관리
- 여러 스토리지 지원
Q: 복원 중에 애플리케이션이 계속 쓰면 어떻게 되나요?
문제: 복원 중에 새 데이터가 들어오면 섞일 수 있습니다.
해결:
- 애플리케이션 중단 (maintenance mode)
- 복원 실행
- 데이터 확인
- 애플리케이션 재시작
또는 새벽 시간대에 작업하세요.
Q: 덤프/복원이 너무 오래 걸려요
대용량 DB (10GB+)의 경우:
압축 사용:
# 덤프 (압축)
mysqldump ... | gzip > backup.sql.gz
# 복원
gunzip < backup.sql.gz | mysql ...병렬 덤프 (mydumper/myloader):
# 설치
brew install mydumper # macOS
apt install mydumper # Ubuntu
# 병렬 덤프
mydumper -h <host> -u doadmin -p <password> \
-B my_app_db,my_service_db \
-o /tmp/backup
# 병렬 복원
myloader -h <host> -u doadmin -p <password> \
-d /tmp/backup훨씬 빠릅니다 (10배 이상).
Q: 특정 테이블만 복원할 수 있나요?
가능합니다:
# 특정 테이블만 덤프
mysqldump ... my_app_db users orders > partial_backup.sql
# 복원
mysql ... my_app_db < partial_backup.sql또는 덤프 파일에서 특정 테이블만 추출:
# users 테이블만 추출
sed -n '/^-- Table structure for table `users`/,/^-- Table structure for table `/p' backup.sql > users_only.sql참고 자료
결론
Claude Code로 개발하다가 실수로 프로덕션 DB를 날려먹었지만, DigitalOcean 자동 백업 덕분에 복구할 수 있었습니다.
핵심 교훈:
- 환경 변수 관리 철저히 - 로컬과 프로덕션 분리
- 위험한 명령어는 항상 재확인 - DROP, DELETE 등
- defaultdb는 함정 - 실제 DB 이름 확인 필수
- GTID는 꺼야 함 -
--set-gtid-purged=OFF필수 - 덤프 파일 크기 확인 - 몇 KB만 나오면 뭔가 잘못된 것
- 자동 백업 만능 아님 - 추가 백업 전략 필요
AI가 아무리 똑똑해도, 최종 책임은 개발자에게 있습니다. 신중하게, 하지만 침착하게.
이 글이 저처럼 DB를 날려먹은 분들께 도움이 되길 바랍니다 🙏
작성일: 2026-01-09 환경: MySQL 8.0, DigitalOcean Managed Database, macOS, doctl 1.145.0 삽질 시간: 약 30분 심리적 데미지: 상당함 얻은 것: 교훈 (비싼 수업료 포함)