MySQL Online DDL이란?
Online DDL은 테이블 구조를 변경하면서도 읽기와 쓰기를 동시에 허용하는 MySQL의 스키마 변경 메커니즘입니다. MySQL 8.0에서 대폭 개선되어 대부분의 ALTER TABLE이 ALGORITHM=INPLACE 또는 INSTANT로 실행 가능해졌습니다. 수억 건 테이블에서도 다운타임 없이 스키마를 변경할 수 있습니다.
DDL 알고리즘 3가지
| 알고리즘 | 동작 방식 | 잠금 | 속도 |
|---|---|---|---|
| INSTANT | 메타데이터만 수정 | 메타데이터 락만 (ms) | 즉시 |
| INPLACE | InnoDB 내부에서 변환 | 시작/끝 메타데이터 락 | 테이블 크기 비례 |
| COPY | 새 테이블 복사 | 전체 테이블 잠금 가능 | 가장 느림 |
INSTANT DDL: 즉시 실행 가능한 작업
MySQL 8.0.12+에서 테이블 크기와 무관하게 즉시 실행되는 작업들입니다.
-- 컬럼 추가 (마지막 위치, 8.0.12+)
ALTER TABLE orders ADD COLUMN notes TEXT, ALGORITHM=INSTANT;
-- 컬럼 추가 (임의 위치, 8.0.29+)
ALTER TABLE orders ADD COLUMN priority INT DEFAULT 0 AFTER status, ALGORITHM=INSTANT;
-- 컬럼 삭제 (8.0.29+)
ALTER TABLE orders DROP COLUMN legacy_field, ALGORITHM=INSTANT;
-- DEFAULT 값 변경
ALTER TABLE orders ALTER COLUMN status SET DEFAULT 'pending', ALGORITHM=INSTANT;
-- ENUM/SET 값 추가 (끝에 추가하는 경우)
ALTER TABLE orders MODIFY COLUMN status
ENUM('draft','pending','confirmed','shipped','cancelled'), ALGORITHM=INSTANT;
-- 테이블 이름 변경
RENAME TABLE orders TO customer_orders;
-- INSTANT 불가능한 경우 자동 폴백 방지
ALTER TABLE orders ADD COLUMN data JSON, ALGORITHM=INSTANT;
-- ERROR: ALGORITHM=INSTANT is not supported. → INPLACE 필요
INPLACE DDL: 온라인 실행 가능한 작업
-- 인덱스 추가 (읽기+쓰기 허용)
ALTER TABLE orders ADD INDEX idx_customer_date (customer_id, created_at),
ALGORITHM=INPLACE, LOCK=NONE;
-- 인덱스 삭제
ALTER TABLE orders DROP INDEX idx_old_index,
ALGORITHM=INPLACE, LOCK=NONE;
-- 컬럼 타입 변경 (일부만 INPLACE 가능)
-- VARCHAR 길이 증가 (255 이하 → 이하, 또는 이상 → 이상)
ALTER TABLE orders MODIFY COLUMN name VARCHAR(200),
ALGORITHM=INPLACE, LOCK=NONE;
-- FULLTEXT 인덱스 추가
ALTER TABLE articles ADD FULLTEXT INDEX ft_content (title, body),
ALGORITHM=INPLACE, LOCK=NONE;
-- 외래키 추가
ALTER TABLE order_items ADD CONSTRAINT fk_order
FOREIGN KEY (order_id) REFERENCES orders(id),
ALGORITHM=INPLACE, LOCK=NONE;
-- AUTO_INCREMENT 값 변경
ALTER TABLE orders AUTO_INCREMENT = 1000000,
ALGORITHM=INPLACE, LOCK=NONE;
위험한 DDL: COPY가 필요한 작업
다음 작업은 테이블 전체를 복사하며, 대형 테이블에서는 수 시간이 걸릴 수 있습니다.
-- ⚠️ 컬럼 타입 변경 (INT → BIGINT)
ALTER TABLE orders MODIFY COLUMN id BIGINT;
-- → 전체 테이블 재구축 필요
-- ⚠️ 문자셋 변경
ALTER TABLE orders CONVERT TO CHARACTER SET utf8mb4;
-- ⚠️ 컬럼 순서 변경 (MySQL 8.0.28 이전)
ALTER TABLE orders MODIFY COLUMN email VARCHAR(255) FIRST;
-- ⚠️ PRIMARY KEY 변경
ALTER TABLE orders DROP PRIMARY KEY, ADD PRIMARY KEY (id, tenant_id);
-- ⚠️ ROW_FORMAT 변경
ALTER TABLE orders ROW_FORMAT=COMPRESSED;
pt-online-schema-change: 대형 테이블 안전 변경
COPY 알고리즘이 필요한 작업은 Percona pt-online-schema-change를 사용합니다.
# 설치
apt-get install percona-toolkit
# INT → BIGINT 변환 (10억 행 테이블)
pt-online-schema-change
--alter "MODIFY COLUMN id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT"
--host=127.0.0.1
--port=3306
--user=admin
--ask-pass
--chunk-size=1000
--max-lag=1s
--check-interval=1
--max-load="Threads_running=50"
--critical-load="Threads_running=200"
--set-vars="lock_wait_timeout=5"
--progress=time,30
--execute
D=mydb,t=orders
# 동작 원리:
# 1. _orders_new 임시 테이블 생성 (새 스키마)
# 2. orders에 TRIGGER 생성 (INSERT/UPDATE/DELETE를 _orders_new에도 반영)
# 3. 청크 단위로 데이터 복사 (--chunk-size)
# 4. 복제 지연 모니터링 (--max-lag)
# 5. 서버 부하 모니터링 (--max-load)
# 6. 완료 후 RENAME TABLE로 원자적 교체
# 복합 변경
pt-online-schema-change
--alter "ADD COLUMN tenant_id INT NOT NULL DEFAULT 1, ADD INDEX idx_tenant (tenant_id)"
--chunk-size=2000
--max-lag=2s
--execute
D=mydb,t=orders
gh-ost: GitHub의 Online Schema Migration
gh-ost는 트리거 대신 바이너리 로그(binlog)를 이용해 스키마를 변경합니다. 트리거의 성능 영향이 우려될 때 사용합니다.
# gh-ost 실행
gh-ost
--host=127.0.0.1
--port=3306
--user=admin
--password=secret
--database=mydb
--table=orders
--alter="MODIFY COLUMN id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT"
--chunk-size=1000
--max-lag-millis=1500
--throttle-query="SELECT IF(COUNT(*)>100, 1, 0) FROM information_schema.processlist WHERE command='Query'"
--cut-over=default
--exact-rowcount
--concurrent-rowcount
--ok-to-drop-table
--initially-drop-ghost-table
--panic-flag-file=/tmp/gh-ost.panic
--execute
# gh-ost vs pt-osc 비교:
# gh-ost: binlog 기반, 트리거 없음, 일시 정지/재개 가능
# pt-osc: 트리거 기반, 더 넓은 호환성, 외래키 지원
# 실행 중 제어
echo "throttle" > /tmp/gh-ost.sock # 일시 정지
echo "no-throttle" > /tmp/gh-ost.sock # 재개
touch /tmp/gh-ost.panic # 긴급 중단
안전한 DDL 실행 체크리스트
-- 1. DDL 알고리즘 사전 확인 (DRY RUN)
ALTER TABLE orders ADD COLUMN test INT, ALGORITHM=INSTANT;
-- ERROR이면 INSTANT 불가 → INPLACE 또는 pt-osc 필요
-- 2. 테이블 크기 확인
SELECT
table_name,
table_rows,
ROUND(data_length / 1024 / 1024) AS data_mb,
ROUND(index_length / 1024 / 1024) AS index_mb
FROM information_schema.tables
WHERE table_schema = 'mydb' AND table_name = 'orders';
-- 3. 실행 중인 DDL 모니터링
SELECT * FROM performance_schema.events_stages_current
WHERE event_name LIKE '%alter%';
-- 4. 메타데이터 락 대기 확인
SELECT * FROM performance_schema.metadata_locks
WHERE object_schema = 'mydb' AND object_name = 'orders';
-- 5. 장시간 트랜잭션 확인 (MDL 대기 원인)
SELECT trx_id, trx_state, trx_started,
TIMESTAMPDIFF(SECOND, trx_started, NOW()) AS duration_sec,
trx_query
FROM information_schema.innodb_trx
ORDER BY trx_started ASC;
-- 6. lock_wait_timeout 설정 (DDL 대기 제한)
SET SESSION lock_wait_timeout = 5;
-- 5초 내 메타데이터 락 획득 못하면 실패 → 재시도
메타데이터 락(MDL) 함정
Online DDL에서 가장 흔한 장애 원인은 메타데이터 락 대기입니다.
-- 시나리오: INPLACE DDL이 "Waiting for table metadata lock"에서 멈춤
-- 원인: 장시간 트랜잭션이 테이블을 참조 중
-- 1. 원인 트랜잭션 찾기
SELECT p.id, p.user, p.host, p.time, p.state, p.info,
t.trx_started, t.trx_state
FROM information_schema.processlist p
JOIN information_schema.innodb_trx t ON p.id = t.trx_mysql_thread_id
WHERE t.trx_started < NOW() - INTERVAL 30 SECOND
ORDER BY t.trx_started;
-- 2. 문제: DDL이 MDL을 기다리면, 이후 모든 SELECT도 대기
-- SELECT → DDL(MDL 대기) → 기존 트랜잭션
-- → 연쇄 대기로 전체 테이블 접근 차단!
-- 해결: DDL 전에 장시간 트랜잭션 정리
-- 또는 lock_wait_timeout을 짧게 설정하고 재시도
SET SESSION lock_wait_timeout = 3;
ALTER TABLE orders ADD INDEX idx_new (column), ALGORITHM=INPLACE, LOCK=NONE;
-- 실패 시 잠시 후 재시도
프로덕션 DDL 운영 전략
- INSTANT 우선: 가능하면 ALGORITHM=INSTANT 사용. 불가능 시 INPLACE → pt-osc/gh-ost 순서
- 피크 시간 회피: 트래픽이 낮은 시간대에 실행
- lock_wait_timeout 설정: DDL 전 3~5초로 설정하여 무한 대기 방지
- 장시간 트랜잭션 정리: DDL 전 30초 이상 실행 중인 트랜잭션 확인
- 레플리카 먼저: Read Replica에서 DDL 실행 후 문제 없으면 Primary에 적용
- 롤백 계획: pt-osc는 원본 테이블을 _old 접미사로 보존. 문제 시 RENAME으로 복구
- 모니터링: Threads_running, Seconds_behind_master, lock wait 실시간 확인
MySQL Online DDL은 운영 중 스키마 변경의 핵심 기술입니다. INSTANT → INPLACE → pt-osc/gh-ost 순서로 접근하면 대부분의 변경을 무중단으로 처리할 수 있습니다. MySQL 인덱스 최적화는 MySQL 인덱스 최적화 가이드를, 파티셔닝 전략은 MySQL Partitioning 글을 참고하세요.