---
title: "디지털오션에서 헷츠너로 무중단 이전, 월 $1,432를 $233로 줄인 24시간 작전"
published: 2026-04-18T13:29:04.000Z
canonical: https://jeff.news/article/1784
---
# 디지털오션에서 헷츠너로 무중단 이전, 월 $1,432를 $233로 줄인 24시간 작전

터키 소프트웨어 회사가 248GB MySQL 데이터와 34개 Nginx 사이트, GitLab EE를 돌리는 프로덕션 서버를 디지털오션에서 헷츠너 전용 서버로 옮긴 실전기. mydumper 기반 병렬 덤프, 마스터-슬레이브 레플리케이션, 구 서버 Nginx를 리버스 프록시로 변신시키는 방식으로 다운타임 0분을 달성했다. 비용은 월 $1,432에서 $233으로 떨어지면서 스펙은 더 좋아진 점이 핵심.

- 터키 소프트웨어 회사가 디지털오션(DigitalOcean) 드롭릿에서 헷츠너(Hetzner) 전용 서버로 넘어가면서 **월 $1,432 → $233**으로 비용을 확 낮춤
  - 연간 절감액 $14,388 (약 2천만 원) — 터키 리라 폭락 + 달러 인플레이션이 겹치면서 달러 과금 인프라가 감당 안 되는 상황
  - 8년 DO 고객이었는데 "정적 워크로드면 전용 서버부터 먼저 알아보라"고 후회 섞인 조언

### 뭘 돌리고 있었나 — 장난감 프로젝트 아님
- MySQL DB 30개 (데이터 248GB), Nginx 가상호스트 34개, GitLab EE(백업만 42GB), Neo4j 그래프DB 30GB
- Supervisor로 백그라운드 워커 수십 개 + Gearman 잡 큐, 모바일 앱 수십만 유저 라이브 트래픽
- 기존 서버는 **CentOS 7** — EOL 지난 지 한참인데 프로덕션에서 그대로 굴리고 있었음. 이번 기회에 AlmaLinux 9.7로 탈출

### 사양 비교 — 가격 반토막인데 스펙이 더 좋음
- CPU: 32 vCPU → 96 논리 CPU (AMD EPYC 9454P, 48코어 × 2 스레드)
- RAM: 192GB → 256GB DDR5
- 스토리지: SSD 600GB + 볼륨 2TB → NVMe Gen4 RAID1 1.92TB

## 무중단 마이그레이션 전략 — 6단계

> [!IMPORTANT]
> 전체 마이그레이션 24시간 소요, **실제 다운타임 0분**. 핵심은 "DNS 바꾸고 재시작, 잘 되길 빌기" 식의 naive한 접근을 거부한 것

- **Phase 1** — 새 서버에 전체 스택 설치
  - Nginx는 동일 플래그로 소스 컴파일, PHP는 Remi repo에 기존 .ini 그대로 복제
  - Let's Encrypt 인증서는 `/etc/letsencrypt/` 디렉토리를 통째로 rsync해 옮긴 뒤, 컷오버 끝나고 전부 force-renew 한 방에 갱신
- **Phase 2** — 웹 파일 rsync 복제
  - `/var/www/html` 65GB, 파일 150만 개를 `--checksum`으로 무결성 검증하며 SSH rsync
  - 컷오버 직전 증분 싱크 한 번 더 돌려서 변경분 커버
- **Phase 3** — MySQL 마스터-슬레이브 실시간 복제
  - 덤프 후 오프라인 복구 방식 대신 **mydumper**로 병렬 벌크 로드 → 덤프 메타데이터의 binlog 포지션에서 레플리케이션 시작
  - 기존 `mysqldump`였으면 며칠 걸릴 작업이 48코어 병렬 덕에 시간 단위로 축소
- **Phase 4** — DNS TTL 축소
  - DO DNS API 스크립트로 A/AAAA 레코드만 TTL 3600 → 300초로 낮춤 (**MX/TXT는 안 건드림** — Google Workspace 메일 전달성 깨질 수 있음)
- **Phase 5** — 구 서버 Nginx를 리버스 프록시로 변신
  - Python 스크립트가 34개 사이트의 모든 `server {}` 블록 파싱해서 프록시 설정으로 치환, 원본은 `.backup`으로 남김
  - `proxy_ssl_verify off` — 양쪽 다 본인이 관리하니 IP 기반 접속이라 검증 끄는 게 자연스러움
- **Phase 6** — DNS 컷오버
  - DO API 스크립트 한 방으로 모든 A 레코드를 새 서버 IP로 전환, 약 10초 만에 완료
  - 구 서버는 일주일간 cold standby로 남겨뒀다가 셧다운

## MySQL 쪽이 제일 험난했다

### mydumper의 승리
- `mydumper --compress`로 덤프 → rsync 전송 → `myloader`로 병렬 임포트
- 48코어 병렬 처리 덕에 "며칠 걸릴 작업을 몇 시간으로" 단축. 큰 DB 옮기는데 아직도 `mysqldump` 쓰면 고생길

### MySQL 5.7 → 8.0 점프의 함정
- `mysqlcheck --check-upgrade`로 호환성 확인은 통과했는데, 임포트 후 `mysql.user` 테이블 컬럼이 45개(원래 51개여야 함)로 나와서 `mysql.infoschema`가 깨지고 인증 불능
  - 업그레이드 재실행하려니 `'sys.innodb_buffer_stats_by_schema' is not VIEW` 에러 — sys 스키마가 뷰가 아니라 일반 테이블로 임포트됨
  - sys 스키마 다시 만들고 업그레이드 재실행해서 해결
- 업그레이드 후 쿼리 실행 시간 체감 단축 — MySQL 8.0 옵티마이저 + InnoDB 개선 효과

### 1062 Duplicate Key 연쇄 에러
- 덤프가 두 번 패스로 떠진 탓에, 그 사이 특정 테이블에 쓰기가 발생 → 임포트 + binlog 재생이 같은 행 중복 INSERT 시도
- `slave_exec_mode=IDEMPOTENT`로 중복키/누락행 에러 조용히 스킵 → 몇 분 안에 `Seconds_Behind_Master: 0`

> [!WARNING]
> 숨어 있던 SUPER 권한 문제 — 레플리카에서 `read_only=1` 걸었는데 PHP 앱 유저들에게 `SUPER` 권한이 붙어 있으면 `read_only`를 그냥 우회해서 쓰기가 들어간다. 앱 유저 24명에서 전부 revoke하고 나서야 정상 동작

### 컷오버 전 테스트 꿀팁
- 로컬 머신 `/etc/hosts`에 새 서버 IP로 도메인 매핑 → 전 세계는 구 서버로 가는데 본인만 새 서버로 붙어서 API/어드민 전부 검증
- 이 검증 끝낸 후에야 실제 DNS 컷오버 진행

## 배운 것
- MySQL 레플리케이션은 무중단 마이그레이션의 절친. 일찍 세팅해두고 따라잡게 두면 자신감 있게 컷오버 가능
- 마이그레이션 전 **MySQL 유저 권한 점검 필수** — `SUPER`가 있으면 `read_only`가 무용지물
- 스크립트 작성은 선택이 아니라 필수. DNS 업데이트, nginx 설정 재작성, 웹훅 일괄 변경 — 34개 사이트를 손으로 했다면 재앙
- `mydumper`+`myloader`가 대용량에선 압도적. 32스레드 병렬이 며칠 → 몇 시간으로 압축
- **정적 워크로드는 클라우드가 비싸다**. 오토스케일이나 일회성 인프라 안 쓰면 전용 서버가 가성비 훨씬 좋음

---

## 기술 맥락

이번 마이그레이션의 백본은 결국 **MySQL 마스터-슬레이브 레플리케이션**이에요. 덤프-복원 방식은 DB 크기가 수백 GB가 되면 "오프라인 윈도우"를 만들 수밖에 없는데, 레플리케이션은 구 서버가 계속 쓰기를 받는 동안 새 서버가 binlog로 따라잡거든요. `Seconds_Behind_Master=0`이 되는 순간이 곧 무중단 컷오버의 "green light"가 되는 거죠.

덤프 도구로 `mysqldump` 대신 **mydumper**를 고른 건 단순 취향이 아니에요. `mysqldump`는 단일 스레드라 수백 GB 앞에서 날 단위로 시간이 늘어나는데, mydumper는 테이블별 병렬 덤프 + `myloader` 병렬 임포트로 48코어 EPYC을 그대로 빨아먹을 수 있어요. 그리고 메타데이터에 binlog 포지션이 기록돼서 레플리케이션 시작점을 정확히 집어낼 수 있다는 게 또 다른 포인트.

`slave_exec_mode=IDEMPOTENT`로 바꾼 이유도 맥락이 있어요. 덤프를 두 패스로 뜨면 그 사이 쓰기가 들어가서 "이미 임포트된 행을 binlog가 또 INSERT하려는" 상황이 생기거든요. 프로덕션이면 절대 쓰면 안 되지만, 초기 캐치업 구간에서만 한시적으로 켜서 중복키/누락행을 흡수하게 하는 게 흔한 패턴이에요.

구 서버 Nginx를 **리버스 프록시로 변신시킨 것**이 무중단의 숨은 주역이에요. DNS TTL을 300초로 낮춰도 지구 반대편 리졸버는 더 오래 캐싱할 수 있거든요. 이때 구 IP로 들어온 요청을 새 서버로 자동 포워딩하면 DNS 전파 완료를 기다릴 필요가 없어요. 이게 "10초 컷오버"의 비밀이에요.

## 핵심 포인트

- 월 $1,432 → $233, 연 $14,388 절감하면서 CPU·RAM·스토리지 전부 업그레이드
- mydumper + myloader 병렬 덤프·임포트로 며칠 작업을 시간 단위로 단축
- MySQL 마스터-슬레이브 레플리케이션을 먼저 세워 Seconds_Behind_Master=0 시점에 컷오버
- 구 서버 Nginx를 스크립트로 리버스 프록시화해 DNS 전파 중에도 무중단 유지
- SUPER 권한이 read_only=1을 우회하는 함정과 MySQL 5.7→8.0 sys 스키마 문제 등 실전 트러블슈팅

## 인사이트

클라우드 프리미엄이 당연시되던 분위기에 '정적 워크로드라면 전용 서버로 내려오는 게 훨씬 이득'이라는 사례. 비용 얘기보다도 mydumper 병렬화, 레플리케이션 기반 컷오버, Nginx 프록시 전환 같은 무중단 마이그레이션 레시피 자체가 참고 자료로 가치 있다.
