MeetBack 2026.08.27 6 min read

백업 파일 생성보다 복원을 먼저 본 MySQL NFS 백업

Replication과 backup의 역할을 나누고 mysqldump·gzip·SHA-256 결과를 NFS에 보관한 뒤 별도 시험 DB에 실제 복원한 과정을 정리했다.

프로젝트 기간: 2026-08-18 ~ 2026-09-02
글 범위: MySQL Logical Backup과 NFS Restore 검증

MySQL Replica가 있으니 backup도 해결됐다고 생각하기 쉽다. 하지만 replication은 Primary의 변경과 실수를 빠르게 복사한다. 잘못된 DELETE나 schema 변경도 Replica에 전달되고, 양쪽 server에 영향을 주는 사고에서는 복구 지점이 되지 못한다.

나는 replication과 backup의 목적을 분리했다.

  • Replica: 최근 data를 따라가는 failover 후보
  • Logical backup: 과거 시점의 data를 다시 가져오기 위한 복구 자료
  • RAID5: disk 한 개 장애에 대한 storage level 보호

세 가지는 서로 대체 관계가 아니다.

Backup은 Primary 한 곳에서만 실행한다

DB1 Primary와 DB2 Replica에 같은 backup unit을 준비했지만 평상시 timer는 Primary에서만 활성화했다. 두 server가 같은 NFS path에 동시에 dump를 만들면 중복 실행과 filename 충돌, 일관되지 않은 역할 관리가 생길 수 있다.

Primary 장애로 Replica를 승격할 때는 database role만 바꾸지 않고 backup timer도 새 Primary로 이동해야 한다. 원래 Primary가 복구돼도 바로 timer를 켜지 않고 Replica로 재편입한 뒤 역할을 다시 정한다.

mysqldump → gzip → SHA-256 흐름

backup job은 다음 순서로 구성했다.

1. 현재 server가 backup 실행 역할인지 확인
2. NFS backup filesystem이 실제 mount됐는지 확인
3. 전용 local MySQL account로 logical dump 생성
4. gzip으로 압축
5. SHA-256 checksum 생성
6. 성공 파일을 최종 이름으로 이동
7. node_exporter textfile metric 기록
8. retention 정책에 따라 오래된 파일 정리

MySQL 계정은 localhost 전용 backup user를 사용하고 필요한 권한만 부여했다. password를 command line에 노출하지 않도록 local authentication 방식을 사용했다.

압축은 storage 사용량과 network write를 줄인다. checksum은 나중에 file이 전송·보관 과정에서 변하지 않았는지 확인하기 위한 것이다. checksum이 있다고 dump 내용이 논리적으로 올바르다는 뜻은 아니므로 restore test가 별도로 필요하다.

NFS가 없을 때 local directory에 쓰지 않게 했다

systemd automount를 사용하면 idle timeout 뒤 실제 mount가 내려갈 수 있다. path directory는 그대로 존재한다. backup script가 -d 같은 directory 존재만 확인하면 NFS가 없는 순간 local root filesystem의 빈 directory에 dump를 저장할 수 있다.

이 상황은 두 가지 문제를 만든다.

  • backup이 NFS에 저장됐다고 착각한다.
  • 큰 dump가 DB server root filesystem을 채운다.

그래서 job 시작 전에 path 접근으로 automount를 깨우고, findmnt와 filesystem type으로 실제 NFS mount인지 확인하며, 전용 user가 write 가능한지도 검증했다. 하나라도 실패하면 dump를 시작하지 않고 실패 metric을 남기도록 했다.

rw Export만으로는 충분하지 않았다

처음에는 NFS export가 rw이고 mount도 성공했지만 backup user의 write가 거부됐다. root_squash와 POSIX permission 때문이었다.

나는 DB server와 NFS server에 같은 numeric UID/GID를 가진 backup 전용 account를 만들었다. NFS directory owner와 mode도 그 identity에 맞췄다. chmod 777이나 no_root_squash로 해결하지 않았다.

DB backup process UID/GID
  = NFS directory owner UID/GID

이후 실제 backup user 권한으로 temporary file 생성과 삭제를 확인했다.

성공 파일만 최종 이름으로 남겼다

dump 중간에 process가 종료되면 절반만 기록된 .sql.gz가 정상 backup처럼 보일 수 있다. 이를 피하기 위해 temporary filename에 먼저 쓰고, dump·압축·checksum이 모두 성공한 뒤 최종 filename으로 원자적으로 이동하는 방식을 사용했다.

job log만으로 성공을 판단하지 않고 다음 정보를 metric으로 기록했다.

  • 마지막 실행 성공 여부
  • 마지막 성공 시각
  • 소요 시간
  • 생성 파일 크기

node_exporter textfile collector를 통해 Prometheus가 읽고, backup 실패와 최근 성공 시각이 오래된 상태를 alert할 수 있게 했다.

Restore 시험으로 실제 사용 가능성을 확인했다

backup의 가장 중요한 검증은 file count가 아니라 restore다. 나는 운영 database에 덮어쓰지 않고 별도 시험 database를 만들었다.

1. backup file checksum 검증
2. gzip stream 검증
3. 별도 test database 생성
4. dump restore
5. table 목록과 주요 row 확인
6. test database 제거

이 과정을 통해 compression file이 열리는지, SQL이 실제로 실행되는지, 계정 권한과 character set 문제가 없는지 확인했다. 단순 mysqldump exit code보다 복구 가능성에 가까운 검증이다.

Daily와 Weekly를 나눴지만 장기 정책은 남아 있다

현재는 daily backup을 만들고 일요일 backup을 weekly copy로 분류하는 구조를 준비했다. 하지만 프로젝트가 끝난 뒤 필요한 보관 기간과 NFS 실제 용량을 기준으로 retention 숫자를 최종 확정해야 한다.

장기적으로 필요한 항목은 다음과 같다.

  • daily·weekly·monthly retention 확정
  • NFS server와 다른 위치의 off-site 2차 copy
  • encryption at rest가 필요한 data 범위
  • 정기 restore drill
  • Primary failover 뒤 timer ownership 자동/수동 확인
  • backup RPO와 RTO 측정

이 구성의 한계

mysqldump는 database가 커질수록 시간이 오래 걸리고 restore도 느릴 수 있다. transactionally consistent option을 사용해도 DDL과 workload 특성을 고려해야 한다. 현재 project 규모에서는 이해하기 쉽고 직접 restore할 수 있는 logical backup이 적합했지만, data가 커지면 physical backup이나 binlog 기반 point-in-time recovery를 검토해야 한다.

이번 작업에서 내가 세운 기준은 “backup 파일이 있다”가 아니라 “필요할 때 다시 database로 만들 수 있다”였다. replication, RAID와 backup의 역할을 분리하고, NFS mount와 권한 실패가 조용히 정상 파일처럼 남지 않도록 검증 단계를 넣었다.