← 아티클 목록

무중단 스키마 변경 — gh-ost·pt-osc로 ALTER 안전하게

2027-05-03#database#MySQL#마이그레이션

운영 중인 서비스에서 ALTER TABLE users ADD COLUMN ...을 그냥 실행했다가 수백만 행 테이블이 수 분간 잠기고, 그 사이 쌓인 커넥션이 풀을 고갈시켜 전체 장애로 번지는 일이 흔합니다. 데이터가 작을 땐 보이지 않던 문제가, 행이 늘면 그대로 사고가 됩니다. 무중단 스키마 변경은 이 잠금을 피해 서비스를 멈추지 않고 테이블 구조를 바꾸는 기법입니다.

왜 직접 ALTER가 위험한가

MySQL은 버전·스토리지 엔진에 따라 일부 변경을 온라인 DDL로 처리하지만, 인덱스 추가나 컬럼 타입 변경 등은 테이블을 통째로 복제(COPY)하며 메타데이터 잠금을 잡습니다. 그 사이 들어오는 쓰기는 대기하고, 대기 커넥션이 max_connections를 넘으면 읽기까지 막힙니다.

방식동작운영 영향
직접 ALTER (COPY)원본 테이블 잠그고 복제변경 내내 쓰기 차단
온라인 DDL (INPLACE)일부 변경만 비차단변경 종류·버전에 따라 제한적
gh-ost / pt-osc그림자 테이블 + 점진 복사거의 무중단, 제어 가능

그림자 테이블 방식의 원리

gh-ost와 pt-online-schema-change는 공통적으로 빈 그림자(shadow) 테이블을 새 구조로 만들고, 원본 데이터를 작은 청크로 복사하면서 그동안의 변경분을 따라잡은 뒤, 마지막에 테이블 이름을 원자적으로 바꿔치기합니다.

차이는 변경분을 잡는 방식입니다.

도구변경 추적부하 제어
pt-osc (Percona)원본에 트리거를 걸어 동기 반영--max-load로 임계 시 일시정지
gh-ost (GitHub)바이너리 로그를 읽어 비동기 반영복제 지연·부하 보고 자동 throttle

트리거 방식인 pt-osc는 쓰기마다 트리거가 같이 실행돼 쓰기 부하가 큰 테이블에서 부담이 됩니다. gh-ost는 트리거 없이 binlog를 소비하므로 원본 쓰기 경로에 영향이 적어, 부하가 큰 운영 환경에서 선호됩니다.

적용 단계 (gh-ost 예시)

  1. 먼저 위험 없이 동작만 보는 --noop로 검증합니다.
로컬 터미널
gh-ost \
  --host=db-primary --database=shop --table=orders \
  --alter="ADD COLUMN coupon_id BIGINT NULL" \
  --noop
  1. 실제 실행은 --execute로 전환하고, 컷오버(이름 바꿔치기)는 수동 트리거로 묶어 트래픽 한가한 시점에 끝냅니다.
로컬 터미널
gh-ost ... --execute \
  --max-load=Threads_running=40 \
  --postpone-cut-over-flag-file=/tmp/ghost.postpone
  1. 진행률과 복제 지연을 보며 --max-load 임계를 넘으면 자동으로 멈추는지 확인합니다. 안전하면 postpone 파일을 지워 컷오버를 마칩니다.

pt-osc도 흐름은 같고, 항상 --dry-run으로 먼저 확인한 뒤 --execute로 바꿉니다.

요점 정리

  • 큰 운영 테이블에 직접 ALTER는 잠금·커넥션 고갈로 장애가 된다.
  • gh-ost·pt-osc는 그림자 테이블에 점진 복사 후 원자적 이름 교체로 무중단을 만든다.
  • 쓰기 부하가 큰 테이블은 트리거 없는 gh-ost가 유리하고, 항상 --noop/--dry-run 검증을 먼저 한다.
  • 컷오버는 트래픽 한산한 시점에 수동으로 묶는다.

운영 DB에서 스키마를 안전하게 바꾸는 절차를 직접 실습해 보려면 데이터베이스 트랙에서 회원가입 없이 무료로 시작할 수 있습니다.