개발자가 실수로 WHERE 절 없이 DELETE를 실행했습니다. 테이블 전체 데이터가 날아갔는데 — 복구할 수 있을까요?

백업이란

백업은 데이터의 복사본을 별도 저장소에 보관 하여, 장애나 실수 발생 시 원래 상태로 되돌릴 수 있도록 준비하는 과정입니다.

백업이 없으면 복구도 없습니다. 레플리카가 있어도 DELETE 같은 논리적 실수는 복제본에도 그대로 반영되기 때문에, 백업은 별도로 반드시 필요합니다.

PLAINTEXT
백업의 핵심 목적:
1. 장애 복구: 서버 고장, 디스크 손상 시 데이터 복원
2. 실수 복구: 잘못된 쿼리 실행으로 인한 데이터 손실 복원
3. 마이그레이션: 서버 이전, 버전 업그레이드 시 데이터 이동

백업 전략 분류

백업 방식은 여러 기준으로 나뉩니다. 각각의 특성을 이해해야 상황에 맞는 전략을 세울 수 있습니다.

논리적 백업 vs 물리적 백업

구분논리적 백업물리적 백업
도구mysqldump, mysqlpumpxtrabackup, MySQL Enterprise Backup
방식SELECT → INSERT SQL 생성데이터 파일(.ibd) 직접 복사
속도느림 (SQL 재실행 필요)빠름 (파일 복사)
호환성다른 버전/엔진 간 이동 가능동일 버전/엔진 필요
크기SQL 텍스트 (압축 가능)데이터 파일 크기와 유사

풀 백업 vs 증분 백업 vs 차등 백업

구분풀 백업증분 백업차등 백업
대상전체 데이터마지막 백업 이후 변경분마지막 풀 백업 이후 변경분
백업 시간가장 김가장 짧음중간
복원 시간가장 짧음가장 김 (풀 + 증분 순차 적용)중간 (풀 + 차등 1회 적용)
저장 공간가장 큼가장 작음중간

온라인 vs 오프라인

구분온라인 백업오프라인 백업
서비스 중단불필요필요 (MySQL 중지)
일관성트랜잭션/락으로 보장파일 시스템 레벨로 보장
사용 상황운영 환경유지보수 윈도우

운영 환경에서는 당연히 온라인 백업이 기본입니다. 서비스를 멈추고 백업한다는 건 현실적으로 어렵기 때문입니다.

mysqldump — 논리적 백업

mysqldump는 MySQL에 기본 포함된 논리적 백업 도구입니다. 가장 오래되고, 가장 널리 쓰이고, 가장 간단합니다.

동작 원리

PLAINTEXT
mysqldump 실행 흐름:

1. MySQL 서버에 접속
2. 대상 테이블에 SELECT 실행
3. 결과를 CREATE TABLE + INSERT INTO ... 형태의 SQL로 변환
4. 표준 출력으로 내보냄 (파일로 리다이렉션)

주요 옵션

BASH
# 전체 데이터베이스 백업
mysqldump -u root -p \
  --all-databases \
  --single-transaction \    # InnoDB: 락 없이 일관된 스냅샷 (핵심 옵션)
  --routines \              # 스토어드 프로시저, 함수 포함
  --triggers \              # 트리거 포함
  --events \                # 이벤트 스케줄러 포함
  --source-data=2 \         # Binlog 포지션을 주석으로 기록 (PITR용)
  > full_backup.sql
BASH
# 특정 데이터베이스만 백업
mysqldump -u root -p \
  --single-transaction \
  --databases myapp_db \
  > myapp_backup.sql
BASH
# 특정 테이블만 백업
mysqldump -u root -p \
  --single-transaction \
  myapp_db orders users \
  > tables_backup.sql

--single-transaction은 InnoDB 테이블에서 가장 중요한 옵션입니다. 이 옵션이 없으면 백업 중에 LOCK TABLES가 걸려서 쓰기가 차단됩니다. 이 옵션을 주면 REPEATABLE READ 트랜잭션 안에서 SELECT하므로, 백업 시점의 일관된 스냅샷을 락 없이 읽을 수 있습니다.

MyISAM 테이블이 섞여 있다면 --lock-tables 또는 --lock-all-tables가 필요합니다. MyISAM은 트랜잭션을 지원하지 않기 때문입니다.

복원

BASH
# 전체 복원
mysql -u root -p < full_backup.sql

# 특정 데이터베이스 복원
mysql -u root -p myapp_db < myapp_backup.sql

복원은 단순히 SQL을 재실행하는 것입니다. 그래서 데이터가 많을수록 시간이 오래 걸립니다.

장점과 단점

**장점 **:

  • MySQL에 기본 포함, 별도 설치 불필요
  • SQL 텍스트이므로 사람이 읽을 수 있고, 부분 복원이 쉬움
  • 다른 MySQL 버전이나 다른 DBMS로 데이터 이동 가능
  • 테이블/데이터베이스 단위 선택적 백업 가능

** 단점 **:

  • 대용량 DB에서 매우 느림 (100GB 이상이면 수 시간)
  • 복원도 느림 (INSERT 하나하나 실행)
  • 백업 중 서버 부하 발생 (대량 SELECT)

공부하다 보니 "그러면 언제 mysqldump를 쓰나?"라는 질문이 자연스럽게 생기는데, 데이터 크기가 수십 GB 이하이거나 스키마만 백업할 때, 또는 다른 환경으로 데이터를 옮겨야 할 때 가장 적합합니다.

Percona XtraBackup — 물리적 백업

데이터가 수백 GB 이상이 되면 mysqldump로는 현실적으로 힘듭니다. 이때 쓰는 것이 Percona XtraBackup입니다.

동작 원리

PLAINTEXT
xtrabackup 동작 흐름:

1. InnoDB 데이터 파일(.ibd)을 직접 복사
2. 복사 중 발생하는 변경은 Redo Log를 캡처하여 별도 저장
3. 복사 완료 후, 캡처된 Redo Log를 데이터 파일에 적용(prepare)
4. 일관된 상태의 백업 완성

SQL을 거치지 않고 파일 자체를 복사하기 때문에, 속도가 mysqldump와는 비교할 수 없을 만큼 빠릅니다.

세 단계: backup → prepare → restore

BASH
# 1단계: 백업 (데이터 파일 복사 + Redo Log 캡처)
xtrabackup --backup \
  --user=root --password=secret \
  --target-dir=/backup/full/

# 2단계: 준비 (Redo Log 적용 → 일관된 상태 만들기)
xtrabackup --prepare \
  --target-dir=/backup/full/

# 3단계: 복원 (MySQL 데이터 디렉토리에 복사)
# 먼저 MySQL을 중지해야 합니다
systemctl stop mysqld

# 기존 데이터 디렉토리 비우기
rm -rf /var/lib/mysql/*

# 백업 파일 복사
xtrabackup --copy-back \
  --target-dir=/backup/full/

# 소유권 복원
chown -R mysql:mysql /var/lib/mysql

# MySQL 재시작
systemctl start mysqld

prepare 단계가 왜 필요한지 처음엔 의아했는데, 생각해보면 당연합니다. 백업하는 동안에도 쓰기가 계속되고 있으므로, 복사된 파일들의 시점이 제각각입니다. prepare에서 Redo Log를 적용하고 미완료 트랜잭션을 롤백해서 일관된 상태로 맞추는 것입니다.

증분 백업

BASH
# 풀 백업 (일요일)
xtrabackup --backup \
  --user=root --password=secret \
  --target-dir=/backup/full/

# 증분 백업 1 (월요일) — 풀 백업 이후 변경분
xtrabackup --backup \
  --user=root --password=secret \
  --target-dir=/backup/inc1/ \
  --incremental-basedir=/backup/full/

# 증분 백업 2 (화요일) — 증분 1 이후 변경분
xtrabackup --backup \
  --user=root --password=secret \
  --target-dir=/backup/inc2/ \
  --incremental-basedir=/backup/inc1/

증분 백업은 LSN(Log Sequence Number)을 기준으로 마지막 백업 이후 변경된 InnoDB 페이지만 복사합니다. 전체를 매번 복사하지 않으니 시간과 저장 공간이 크게 절약됩니다.

BASH
# 증분 백업 복원 순서
# 1. 풀 백업 prepare (redo만 적용, undo 롤백은 하지 않음)
xtrabackup --prepare --apply-log-only \
  --target-dir=/backup/full/

# 2. 증분 1 적용
xtrabackup --prepare --apply-log-only \
  --target-dir=/backup/full/ \
  --incremental-dir=/backup/inc1/

# 3. 증분 2 적용 (마지막 증분에서는 --apply-log-only 생략)
xtrabackup --prepare \
  --target-dir=/backup/full/ \
  --incremental-dir=/backup/inc2/

# 4. 복원
xtrabackup --copy-back --target-dir=/backup/full/

복원할 때는 풀 백업 위에 증분을 순서대로 겹쳐 적용합니다. --apply-log-only는 마지막이 아닌 중간 단계에서 undo 롤백을 건너뛰기 위한 옵션입니다.

장점과 단점

**장점 **:

  • 백업/복원 속도가 매우 빠름 (대용량 DB에 적합)
  • 온라인 백업 가능 (서비스 중단 없음)
  • 증분 백업 지원으로 저장 공간 절약
  • 압축, 암호화, 스트리밍 기능 제공

** 단점 **:

  • 동일 MySQL 버전에서만 복원 가능
  • InnoDB 전용 (MyISAM은 별도 처리)
  • 복원 시 MySQL 중지 필요
  • 별도 설치 필요

Binlog와 PITR (시점 복구)

풀 백업만으로는 "오늘 오후 2시 30분 상태로 돌려줘"라는 요청에 대응할 수 없습니다. 백업 시점과 장애 시점 사이의 변경사항을 복원하려면 Binlog + PITR 이 필요합니다.

PITR의 원리

PLAINTEXT
PITR 복구 흐름:

1. 가장 최근 전체 백업을 복원
2. 백업 시점 이후의 Binlog를 확인
3. mysqlbinlog로 원하는 시점까지의 이벤트를 추출
4. 추출된 SQL을 MySQL에 재실행

예시: 매일 새벽 3시 풀 백업, 오후 2시 30분에 사고 발생
→ 새벽 3시 백업 복원 + 3시~2시 29분 Binlog 재생

Binlog 확인

SQL
-- 현재 Binlog 파일과 포지션 확인
SHOW MASTER STATUS;
-- +------------------+----------+
-- | File             | Position |
-- +------------------+----------+
-- | mysql-bin.000015 |  1234567 |
-- +------------------+----------+

-- Binlog 이벤트 목록 확인
SHOW BINLOG EVENTS IN 'mysql-bin.000015' LIMIT 20;

mysqlbinlog로 시점 복구

BASH
# Binlog 내용을 사람이 읽을 수 있는 형태로 확인
mysqlbinlog --base64-output=DECODE-ROWS --verbose \
  /var/lib/mysql/mysql-bin.000015

# 특정 시간 범위의 이벤트만 추출하여 복원
mysqlbinlog \
  --start-datetime="2026-03-28 03:00:00" \  # 백업 시점 이후부터
  --stop-datetime="2026-03-28 14:29:00" \   # 사고 직전까지
  /var/lib/mysql/mysql-bin.000015 \
  | mysql -u root -p
BASH
# 특정 포지션 범위로도 가능
mysqlbinlog \
  --start-position=154 \
  --stop-position=1234567 \
  /var/lib/mysql/mysql-bin.000015 \
  | mysql -u root -p
BASH
# Binlog 파일이 여러 개인 경우 순서대로 나열
mysqlbinlog \
  --stop-datetime="2026-03-28 14:29:00" \
  mysql-bin.000013 mysql-bin.000014 mysql-bin.000015 \
  | mysql -u root -p

--stop-datetime은 해당 시각 직전까지 의 이벤트를 포함합니다. 잘못된 DELETE가 14:30:00에 실행되었다면, 14:29:59까지 복구하면 됩니다.

PITR 실전 시나리오

누군가가 오후 2시 30분에 DELETE FROM orders; (WHERE 절 없이)를 실행했다고 가정합니다.

BASH
# 1. 먼저 새벽 3시 전체 백업을 복원
mysql -u root -p < /backup/full_20260328_0300.sql

# 2. 백업에 기록된 Binlog 포지션 확인
# mysqldump의 --source-data 옵션이 기록해둔 주석
head -30 /backup/full_20260328_0300.sql | grep "CHANGE MASTER"
# -- CHANGE MASTER TO MASTER_LOG_FILE='mysql-bin.000014', MASTER_LOG_POS=4567;

# 3. 해당 포지션부터 사고 직전까지의 Binlog 적용
mysqlbinlog \
  --start-position=4567 \
  /var/lib/mysql/mysql-bin.000014 \
  --stop-datetime="2026-03-28 14:29:00" \
  /var/lib/mysql/mysql-bin.000015 \
  | mysql -u root -p

이렇게 하면 새벽 3시 백업 시점부터 오후 2시 29분까지의 모든 변경사항이 복원됩니다.

백업 전략 설계

실무에서는 하나의 백업 방식만 쓰지 않습니다. 풀 백업 + 증분 백업 + Binlog를 조합하는 것이 일반적입니다.

RPO와 RTO

백업 전략을 세우기 전에 두 가지 지표를 먼저 정해야 합니다.

지표의미예시
RPO (Recovery Point Objective)최대 허용 데이터 손실량"최대 1시간 분량의 데이터 손실까지 허용"
RTO (Recovery Time Objective)최대 허용 복구 시간"장애 발생 후 30분 이내 복구"

RPO가 짧을수록 더 자주 백업해야 하고, RTO가 짧을수록 복원 속도가 빨라야 합니다.

예시: 중소규모 서비스 백업 스케줄

PLAINTEXT
백업 전략 예시 (DB 크기: ~50GB)

┌──────────────────────────────────────────────────┐
│  일요일 02:00   풀 백업 (xtrabackup --backup)     │
│  월~토 02:00    증분 백업 (xtrabackup --incremental)│
│  상시           Binlog 실시간 기록                  │
│  매 6시간       Binlog를 원격 스토리지에 복사        │
└──────────────────────────────────────────────────┘

RPO: 최대 6시간 (Binlog 원격 복사 주기)
RTO: ~30분 (풀 백업 + 증분 적용 + Binlog 재생)
PLAINTEXT
소규모 서비스라면 더 단순하게:

매일 새벽  mysqldump --single-transaction 풀 백업
상시      Binlog 활성화 + 7일 보관
주 1회    백업 파일을 S3 같은 원격 저장소에 업로드

보관 정책

PLAINTEXT
보관 기간 예시:
- 일일 백업: 7일 보관
- 주간 풀 백업: 4주 보관
- 월간 풀 백업: 12개월 보관
- Binlog: 최소 7일 (다음 풀 백업까지는 반드시 보관)

Binlog 보관 기간은 특히 중요합니다. 풀 백업과 Binlog 사이에 빈 구간이 생기면 PITR이 불가능해집니다.

실무 주의사항

백업 검증 — 복원 테스트

백업이 있다고 안심하면 안 됩니다. 복원이 되는지 확인하지 않은 백업은 백업이 아닙니다.

BASH
# 주기적으로 복원 테스트 (별도 서버에서)
# 1. 백업 파일이 정상인지
mysql -u root -p test_restore_db < /backup/full_backup.sql
echo $?  # 0이면 성공

# 2. 데이터 건수 검증
mysql -u root -p -e "SELECT COUNT(*) FROM test_restore_db.orders;"

실제로 "백업은 매일 돌고 있었는데, 막상 복원하려니 파일이 깨져 있었다"는 사례가 적지 않습니다. 최소 월 1회는 복원 테스트를 하는 것이 좋습니다.

백업 파일 저장 위치

PLAINTEXT
절대 하면 안 되는 것:
  ✗ 백업 파일을 MySQL 서버와 같은 디스크에 보관

올바른 방법:
  ✓ 별도 서버나 NAS에 저장
  ✓ S3, GCS 같은 클라우드 오브젝트 스토리지에 업로드
  ✓ 가능하면 다른 리전에도 복제

서버의 디스크가 고장나면 데이터와 백업이 동시에 날아갑니다. 백업의 의미가 없어지는 것입니다.

모니터링과 알림

BASH
# 백업 스크립트 예시 — 실패 시 알림 발송
#!/bin/bash
BACKUP_DIR="/backup/$(date +%Y%m%d)"

xtrabackup --backup \
  --user=backup_user --password=secret \
  --target-dir="$BACKUP_DIR" 2>/tmp/backup.log

if [ $? -ne 0 ]; then
  # 슬랙이나 이메일로 알림
  curl -X POST -H 'Content-type: application/json' \
    --data '{"text":"[ALERT] MySQL 백업 실패!"}' \
    https://hooks.slack.com/services/YOUR/WEBHOOK/URL
  exit 1
fi

# 백업 크기 확인 (비정상적으로 작으면 경고)
SIZE=$(du -sm "$BACKUP_DIR" | awk '{print $1}')
if [ "$SIZE" -lt 100 ]; then
  curl -X POST -H 'Content-type: application/json' \
    --data "{\"text\":\"[WARN] 백업 크기가 비정상적으로 작습니다: ${SIZE}MB\"}" \
    https://hooks.slack.com/services/YOUR/WEBHOOK/URL
fi

echo "백업 완료: $BACKUP_DIR (${SIZE}MB)"

백업이 실패했는데 아무도 모르고 며칠이 지나면, 복구가 필요한 시점에 백업이 없는 최악의 상황이 됩니다.

정리

도구방식적합한 상황
mysqldump논리적 (SQL)소규모 DB, 스키마 이동, 부분 백업
xtrabackup물리적 (파일 복사)대규모 DB, 빠른 백업/복원 필요
Binlog + PITR시점 복구특정 시점으로 정밀 복구
PLAINTEXT
백업 3원칙:
1. 백업은 반드시 원격 저장소에 보관한다
2. 복원 테스트를 주기적으로 수행한다
3. 백업 성공/실패를 모니터링한다

처음 글에서 던진 질문으로 돌아가면 — WHERE 없이 DELETE를 실행해서 데이터가 날아갔더라도, 전체 백업과 Binlog가 있다면 PITR로 사고 직전 시점까지 복구할 수 있습니다. 반대로 백업이 없었다면? 그건 정말 답이 없습니다. 백업은 보험입니다. 사고가 나기 전에 들어야 합니다.

댓글 로딩 중...