오전 배포 직전, 개발팀에서 "DB 컬럼 하나 추가해야 합니다"라는 요청이 왔습니다. 간단해 보입니다. 그런데 그 테이블에 데이터가 5000만 건 있습니다. ALTER TABLE을 실행하는 순간 테이블에 Lock이 걸리고, 그 시간 동안 서비스의 모든 쓰기 요청이 블로킹됩니다. 몇 분이면 되는 줄 알았던 작업이 20분짜리 서비스 장애로 변할 수 있습니다.
이 모듈은 운영 DB 스키마 변경을 안전하게 처리하는 절차를 다룹니다. DDL 반영 전 체크리스트, Flyway 버전 관리, 대용량 테이블 무중단 변경, 롤백 준비까지 포함합니다.
- 1DDL 반영 전 체크리스트(백업/영향도/Lock 예상/롤백 스크립트)를 작성할 수 있다
- 2개발DB → 검증DB → 운영DB 순서의 반영 절차를 설명할 수 있다
- 3Flyway 마이그레이션 파일 명명 규칙을 이해하고 실행 이력을 확인할 수 있다
- 4pt-online-schema-change를 써야 하는 상황(대용량 테이블)을 판단할 수 있다
- 5잘못된 DDL 반영 후 롤백 스크립트를 실행하는 절차를 따라할 수 있다
DDL 반영 절차 — 반드시 이 순서로
개발 → 검증 → 운영 순서와 체크리스트
운영 DB에 DDL을 곧바로 적용하는 것은 위험합니다. 검증되지 않은 SQL이 운영 데이터를 망가뜨리거나 서비스를 중단시킬 수 있습니다. 항상 아래 순서를 따릅니다.

1. 개발 DB (dev) → SQL 작성 및 기본 검증
2. 스테이징 DB (stg) → 운영과 동일한 데이터 구조로 검증
3. 운영 DB (prod) → 체크리스트 통과 후 최종 반영
운영 DB DDL 반영 전 체크리스트:
| 항목 | 확인 방법 | 필수 여부 |
|---|---|---|
| DB 백업 완료 | mysqldump 또는 스냅샷 확인 | 필수 |
| 영향 테이블 크기 확인 | SHOW TABLE STATUS | 필수 |
| Lock 예상 시간 산정 | 스테이징에서 실행 시간 측정 | 필수 |
| 접속 세션 수 확인 | SHOW PROCESSLIST | 필수 |
| 롤백 스크립트 준비 | UNDO SQL 작성 완료 | 필수 |
| 점검 시간 또는 트래픽 저점 확인 | 모니터링 대시보드 | 필수 |
# 영향 테이블 크기 확인
mysql -h db-server -u dba -p mydb -e "
SELECT
table_name,
table_rows,
ROUND((data_length + index_length) / 1024 / 1024, 2) AS size_mb
FROM information_schema.tables
WHERE table_schema = 'mydb'
AND table_name = 'users';
"
# 현재 접속 세션 수 확인
mysql -h db-server -u dba -p mydb -e "SHOW PROCESSLIST;"
mysql -h db-server -u dba -p mydb -e "SELECT COUNT(*) FROM information_schema.processlist;"
DDL 실행과 확인
운영 DB에서 DDL 반영하는 방법
실제 DDL을 운영 DB에 적용하는 명령입니다. SQL 파일로 관리해서 실수를 줄이고, 적용 전후 스키마를 확인합니다.

# 1. 백업 확인 (반드시 먼저)
ls -lh /backup/mydb_$(date +%Y%m%d)*.sql 2>/dev/null || echo "WARNING: 오늘 백업 없음"
# 2. DDL 파일 내용 최종 확인
cat /tmp/V10__add_user_phone_column.sql
# ALTER TABLE users ADD COLUMN phone VARCHAR(20) NULL AFTER email;
# 3. 스테이징에서 실행 시간 측정 (운영 전 반드시)
time mysql -h stg-db-server -u dba -p mydb < /tmp/V10__add_user_phone_column.sql
# 4. 운영 DB 적용
mysql -h db-server -u dba -p mydb < /tmp/V10__add_user_phone_column.sql
# 5. 적용 결과 확인
mysql -h db-server -u dba -p mydb -e "SHOW COLUMNS FROM users LIKE 'phone';"
# 6. 스키마 전체 확인
mysql -h db-server -u dba -p mydb -e "DESCRIBE users;"
롤백 스크립트 — 모든 DDL에 대응하는 UNDO SQL:
-- V10__add_user_phone_column.sql (적용)
ALTER TABLE users ADD COLUMN phone VARCHAR(20) NULL AFTER email;
-- V10__undo_add_user_phone_column.sql (롤백용)
ALTER TABLE users DROP COLUMN phone;
# 잘못 적용됐을 때 롤백
mysql -h db-server -u dba -p mydb < /tmp/V10__undo_add_user_phone_column.sql
mysql -h db-server -u dba -p mydb -e "DESCRIBE users;" # 컬럼 사라졌는지 확인
Flyway — DB 마이그레이션 버전 관리
Flyway 마이그레이션 파일 구조
Flyway는 SQL 스크립트를 버전 순서대로 실행하고 이력을 기록하는 도구입니다. "어느 환경에 어느 SQL까지 반영됐나"를 추적할 수 있어 환경 간 스키마 불일치를 방지합니다.
파일 명명 규칙:
V{버전}__{설명}.sql
src/main/resources/db/migration/
├── V1__create_users_table.sql
├── V2__create_orders_table.sql
├── V3__add_email_index.sql
├── V9__add_product_category.sql
└── V10__add_user_phone_column.sql
-- V10__add_user_phone_column.sql
ALTER TABLE users ADD COLUMN phone VARCHAR(20) NULL AFTER email;
CREATE INDEX idx_users_phone ON users(phone);
# Spring Boot + Flyway 마이그레이션 실행
# 애플리케이션 기동 시 자동 실행, 또는 수동 실행:
mvn flyway:migrate -Dflyway.url=jdbc:mysql://db-server:3306/mydb \
-Dflyway.user=dba -Dflyway.password=${DB_PASSWORD}
# 마이그레이션 이력 확인
mvn flyway:info
# 또는 DB에서 직접 확인
mysql -h db-server -u dba -p mydb -e "SELECT * FROM flyway_schema_history ORDER BY installed_rank DESC LIMIT 10;"
+--------------+---------+------------------------------+---------+---------------------+---------+
| installed_rank | version | description | success | installed_on | checksum|
+--------------+---------+------------------------------+---------+---------------------+---------+
| 10 | 10 | add user phone column | 1 | 2026-05-30 10:30:00 | 12345678|
| 9 | 9 | add product category | 1 | 2026-05-28 15:00:00 | 87654321|
대용량 테이블 무중단 변경
pt-online-schema-change — Lock 없는 ALTER
테이블 크기가 수천만 건 이상이면 일반 ALTER TABLE은 장시간 Lock을 유발합니다. pt-online-schema-change(pt-osc)는 이 문제를 해결하는 Percona 도구입니다. 원본 테이블을 사용하면서 백그라운드에서 새 테이블에 데이터를 복사한 뒤 atomic rename으로 교체합니다.
# pt-online-schema-change 설치
yum install percona-toolkit
# 또는
apt-get install percona-toolkit
# 기본 사용법 (컬럼 추가 예시)
pt-online-schema-change \
--alter "ADD COLUMN phone VARCHAR(20) NULL AFTER email" \
--host=db-server \
--user=dba \
--password=${DB_PASSWORD} \
--database=mydb \
--table=users \
--execute
# 실행 전 dry-run (실제 실행 없이 계획만 출력)
pt-online-schema-change \
--alter "ADD COLUMN phone VARCHAR(20) NULL AFTER email" \
--host=db-server --user=dba --password=${DB_PASSWORD} \
--database=mydb --table=users \
--dry-run
# 진행 상황 모니터링 (별도 터미널)
watch -n 5 "mysql -h db-server -u dba -p mydb -e 'SHOW TABLE STATUS LIKE \"_users_new\"'"
pt-osc 사용 판단 기준:
| 테이블 크기 | 권장 방법 |
|---|---|
| 100만 건 이하 | 일반 ALTER TABLE (Lock 몇 초) |
| 100만~1000만 건 | 트래픽 저점(새벽 2~4시)에 ALTER TABLE |
| 1000만 건 초과 | pt-online-schema-change 사용 |
실습
DDL 적용 전 영향받는 테이블의 크기와 현재 접속 세션을 확인합니다. 이 정보로 Lock 시간을 예측하고 작업 시간대를 결정합니다.
# 영향 테이블 크기 확인
mysql -h localhost -u root -p mydb -e "
SELECT table_name,
table_rows,
ROUND((data_length+index_length)/1024/1024, 2) AS size_mb
FROM information_schema.tables
WHERE table_schema = 'mydb' AND table_name = 'users';"
# 현재 진행 중인 쿼리 확인
mysql -h localhost -u root -p -e "SHOW FULL PROCESSLIST;" | grep -v "Sleep"
# 테이블 Lock 상태 확인
mysql -h localhost -u root -p -e "SHOW OPEN TABLES WHERE In_use > 0;"
+------------+------------+---------+
| table_name | table_rows | size_mb |
+------------+------------+---------+
| users | 52483201 | 4523.45 |
+------------+------------+---------+
mysql -h localhost -u root -p -e 'SELECT table_name, table_rows, ROUND((data_length+index_length)/1024/1024,2) AS size_mb FROM information_schema.tables WHERE table_schema=DATABASE() ORDER BY size_mb DESC LIMIT 10;'- 영향 테이블의 table_rows가 1000만 건을 초과하는가 — 초과하면 pt-osc 사용 검토
- SHOW PROCESSLIST 에 장시간 실행 중인 쿼리(Time 컬럼 큰 값)가 없는가
- 트래픽이 낮은 시간대(새벽)인가 — 아니라면 점검 시간 설정 검토
- 롤백용 UNDO SQL 파일이 준비됐는가
Flyway가 적용된 프로젝트라면 flyway_schema_history 테이블에서 현재 반영된 스키마 버전을 확인할 수 있습니다. 어느 환경에 어느 버전까지 적용됐는지 추적하는 데 사용합니다.
# flyway_schema_history 이력 확인
mysql -h localhost -u root -p mydb -e "
SELECT installed_rank, version, description, success, installed_on
FROM flyway_schema_history
ORDER BY installed_rank DESC LIMIT 5;"
# 아직 적용 안 된 마이그레이션 확인 (Spring Boot 프로젝트에서)
mvn flyway:info -Dflyway.url=jdbc:mysql://localhost:3306/mydb \
-Dflyway.user=root -Dflyway.password=secret 2>/dev/null | grep "Pending\|Success"
+--------------+---------+---------------------------+---------+---------------------+
| installed_rank | version | description | success | installed_on |
+--------------+---------+---------------------------+---------+---------------------+
| 10 | 10 | add user phone column | 1 | 2026-05-30 10:30:00 |
| 9 | 9 | add product category | 1 | 2026-05-28 15:00:00 |
mysql -h localhost -u root -p mydb -e 'SELECT installed_rank, version, description, success, installed_on FROM flyway_schema_history ORDER BY installed_rank DESC LIMIT 5;'- flyway_schema_history 에서 최신 버전의 success 컬럼이 1인가 (0이면 실패한 마이그레이션)
- 현재 환경의 버전이 다른 환경(stg, prod)과 일치하는가
- 실패한 마이그레이션(success=0)이 있다면 DESCRIBE 로 실제 스키마 상태 확인 필요
- Pending 상태의 마이그레이션이 예상한 개수와 일치하는가
트러블슈팅
원인: 다른 세션이 테이블을 사용하는 상태에서 ALTER TABLE이 메타데이터 Lock을 기다리고 있습니다. ALTER 자체가 Lock을 걸기도 하지만, 이미 열린 트랜잭션이 있으면 그 트랜잭션이 끝날 때까지 대기합니다.
# 1. Lock 대기 상태 확인
mysql -e "SELECT * FROM information_schema.INNODB_LOCK_WAITS;"
mysql -e "SELECT r.trx_id waiting_trx, r.trx_mysql_thread_id waiting_thread,
b.trx_id blocking_trx, b.trx_mysql_thread_id blocking_thread
FROM information_schema.INNODB_LOCK_WAITS w
JOIN information_schema.INNODB_TRX b ON b.trx_id = w.blocking_trx_id
JOIN information_schema.INNODB_TRX r ON r.trx_id = w.requesting_trx_id;"
# 2. 블로킹 세션 확인 및 종료 (DBA 판단 후)
mysql -e "SHOW FULL PROCESSLIST;" | grep -v Sleep
# 장시간 실행 중인 세션 ID 확인 후
mysql -e "KILL <세션ID>;"
# 3. 즉시 대안 — ALTER 취소 후 pt-osc로 전환
# Ctrl+C 로 ALTER 취소 (완료 전이면 테이블 원상 복귀)
# pt-online-schema-change 사용
pt-online-schema-change \
--alter "ADD COLUMN phone VARCHAR(20) NULL AFTER email" \
--host=localhost --user=dba --password=${DB_PASSWORD} \
--database=mydb --table=users \
--execute --no-drop-old-table
재발 방지: 테이블 크기를 DDL 전에 항상 확인하고, 1000만 건 이상은 pt-osc를 기본으로 사용합니다.
원인: 롤백 스크립트가 준비되지 않은 상태에서 잘못된 DDL이 실행됐습니다. DROP COLUMN은 해당 컬럼의 데이터까지 삭제합니다.
# 즉각 조치 순서
# 1. 추가 변경 즉시 중단
# 더 이상의 DML(INSERT/UPDATE/DELETE) 최소화
# 2. 백업에서 해당 컬럼 데이터 복구 가능 여부 확인
mysqldump --no-create-info mydb users > /tmp/users_backup_check.sql
grep "phone" /tmp/users_backup_check.sql | head -5
# 3. 백업 DB에서 데이터 추출
mysql -h backup-db-server -u dba -p mydb -e "
SELECT id, phone FROM users LIMIT 10;"
# 4. 컬럼 재생성 후 백업 데이터 복구
mysql -h db-server -u dba -p mydb -e "
ALTER TABLE users ADD COLUMN phone VARCHAR(20) NULL AFTER email;"
# 백업 데이터로 UPDATE
mysql -h db-server -u dba -p mydb << 'EOF'
UPDATE users u
JOIN backup_users b ON u.id = b.id
SET u.phone = b.phone
WHERE b.phone IS NOT NULL;
EOF
# 5. 복구 건수 확인
mysql -h db-server -u dba -p mydb -e "
SELECT COUNT(*) FROM users WHERE phone IS NOT NULL;"
핵심 교훈: DROP COLUMN, DROP TABLE, TRUNCATE를 포함한 모든 DDL은 실행 전 UNDO SQL을 준비합니다. 백업이 없으면 DROP으로 삭제된 데이터는 복구 불가능합니다.
실제 업무에서 이 지식이 쓰이는 상황:
DB 스키마 변경은 인프라 엔지니어와 DBA가 협업하는 대표적인 작업입니다.
1. 정기 배포 시 DDL 반영 절차:
# 배포 당일 오전 체크리스트 실행
# ① 백업 확인
ls -lh /backup/$(date +%Y%m%d)/mydb*.sql
# ② 스테이징에서 DDL 실행 시간 측정
time mysql -h stg-db -u dba -p mydb < /deploy/V10__add_column.sql
# ③ 운영 적용 (트래픽 저점 시간 또는 점검 시간)
mysql -h prod-db -u dba -p mydb < /deploy/V10__add_column.sql
# ④ 검증
mysql -h prod-db -u dba -p mydb -e "DESCRIBE users;" | grep phone
2. Flyway로 스키마 버전 관리: 인프라 팀이 여러 환경(dev/stg/prod)의 스키마 버전을 추적합니다.
# 각 환경의 현재 Flyway 버전 확인 스크립트
for env in dev stg prod; do
echo "=== $env ==="
mysql -h ${env}-db -u dba -p${DB_PASS} mydb \
-e "SELECT MAX(version) as current_version, MAX(installed_on) as last_migration FROM flyway_schema_history WHERE success=1;"
done
3. 긴급 롤백 판단: DDL 문제는 애플리케이션 재배포로는 해결되지 않습니다. 스키마 자체를 되돌려야 합니다. 그래서 롤백 스크립트는 배포 패키지와 함께 항상 준비해야 합니다. 다음 모듈에서는 이렇게 DB까지 준비된 상태에서 애플리케이션을 배포하는 전체 배포 구조와 스크립트를 다룹니다.