이 글에서 얻는 것
- DB 마이그레이션이 왜 필요한지, 어떤 문제를 해결하는지 이해합니다.
- Flyway로 데이터베이스 스키마를 버전 관리할 수 있습니다.
- 마이그레이션 파일을 작성하고, 안전하게 적용할 수 있습니다.
- 롤백 전략과 장애 대응 방법을 익힙니다.
0) DB 마이그레이션은 “스키마의 Git"이다
왜 필요한가?
문제 상황:
개발자 A: "로컬에서는 되는데..."
개발자 B: "내 DB에는 그 컬럼이 없는데요?"
운영 서버: "배포했더니 테이블이 없다고 에러가..."
원인:
- 수동 SQL 실행 → 누가, 언제 실행했는지 모름
- 환경마다 스키마가 다름
- 버전 관리 안 됨
해결: DB 마이그레이션 도구
마이그레이션 도구:
- 스키마 변경을 코드로 관리
- Git으로 버전 관리
- 자동 적용 (환경 간 일관성)
- 실행 이력 추적
1) Flyway vs Liquibase
1-1) 비교
Flyway:
- 간단한 설정
- SQL 기반 (익숙함)
- 빠른 학습 곡선
- 커뮤니티 에디션 (무료)
Liquibase:
- XML/YAML/JSON 지원
- DB 독립적 (여러 DB 지원)
- 더 많은 기능
- 복잡한 설정
추천: Flyway (시작하기 쉬움)
1-2) Flyway 동작 원리
1. flyway_schema_history 테이블 생성 (최초 실행 시)
- version, description, checksum, installed_on 등 저장
2. 마이그레이션 파일 탐색
- src/main/resources/db/migration/
3. 미실행 파일 찾기
- checksum으로 변경 감지
4. 순서대로 실행
- V1, V2, V3...
5. 이력 기록
- flyway_schema_history에 저장
2) Flyway 설정
2-1) Spring Boot 설정
의존성:
// build.gradle
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.flywaydb:flyway-core'
runtimeOnly 'com.mysql:mysql-connector-j'
}
application.yml:
spring:
datasource:
url: jdbc:mysql://localhost:3306/mydb
username: user
password: password
flyway:
enabled: true # Flyway 활성화 (기본: true)
baseline-on-migrate: true # 기존 DB에 Flyway 적용 시
locations: classpath:db/migration # 마이그레이션 파일 위치
sql-migration-prefix: V # 버전 파일 접두사
sql-migration-separator: __ # 구분자
sql-migration-suffixes: .sql # 파일 확장자
validate-on-migrate: true # 실행 전 검증
2-2) 디렉토리 구조
src/main/resources/
└── db/
└── migration/
├── V1__create_users_table.sql
├── V2__add_email_to_users.sql
├── V3__create_orders_table.sql
└── V4__add_index_on_user_email.sql
파일명 규칙:
V{버전}__{설명}.sql
V: Version (필수)
{버전}: 1, 2, 3 또는 1.0, 1.1, 2.0 (순서대로 실행)
__: 구분자 (언더스코어 2개)
{설명}: 영문/숫자/언더스코어 (공백 불가)
.sql: 확장자
예:
V1__create_users_table.sql
V2__add_email_to_users.sql
V2.1__add_index_on_email.sql
3) 마이그레이션 파일 작성
3-1) 테이블 생성
V1__create_users_table.sql:
CREATE TABLE users (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_username ON users(username);
3-2) 컬럼 추가
V2__add_email_to_users.sql:
ALTER TABLE users
ADD COLUMN email VARCHAR(255);
ALTER TABLE users
ADD CONSTRAINT uk_email UNIQUE (email);
3-3) 테이블 추가
V3__create_orders_table.sql:
CREATE TABLE orders (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT NOT NULL,
amount DECIMAL(10, 2) NOT NULL,
status VARCHAR(20) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id)
);
CREATE INDEX idx_user_id ON orders(user_id);
CREATE INDEX idx_created_at ON orders(created_at);
3-4) 데이터 마이그레이션
V4__migrate_user_data.sql:
-- 기존 데이터 업데이트
UPDATE users
SET email = CONCAT(username, '@example.com')
WHERE email IS NULL;
-- 기본값 설정 후 NOT NULL 제약 추가
ALTER TABLE users
MODIFY COLUMN email VARCHAR(255) NOT NULL;
3-5) 복잡한 마이그레이션
V5__refactor_user_status.sql:
-- 1. 새 컬럼 추가
ALTER TABLE users
ADD COLUMN status VARCHAR(20);
-- 2. 기존 데이터 마이그레이션
UPDATE users
SET status = CASE
WHEN deleted_at IS NOT NULL THEN 'DELETED'
WHEN email_verified = 1 THEN 'ACTIVE'
ELSE 'PENDING'
END;
-- 3. NOT NULL 제약 추가
ALTER TABLE users
MODIFY COLUMN status VARCHAR(20) NOT NULL;
-- 4. 기존 컬럼 제거 (선택적)
-- ALTER TABLE users
-- DROP COLUMN deleted_at,
-- DROP COLUMN email_verified;
4) 실행과 확인
4-1) 자동 실행
# Spring Boot 애플리케이션 시작 시 자동 실행
./gradlew bootRun
# 로그:
# Flyway: Migrating schema `mydb` to version "1 - create users table"
# Flyway: Migrating schema `mydb` to version "2 - add email to users"
# Flyway: Successfully applied 2 migrations
4-2) 마이그레이션 이력 확인
-- flyway_schema_history 테이블 조회
SELECT
installed_rank,
version,
description,
type,
script,
checksum,
installed_on,
execution_time,
success
FROM flyway_schema_history
ORDER BY installed_rank;
-- 결과:
-- rank | version | description | script | installed_on | success
-- 1 | 1 | create users table | V1__create_users_table.sql | 2025-12-16 10:00:00 | 1
-- 2 | 2 | add email to users | V2__add_email_to_users.sql | 2025-12-16 10:00:01 | 1
5) 롤백 전략
5-1) Flyway는 자동 롤백 미지원
Flyway Community Edition:
- 롤백 기능 없음
- Pro 버전에서만 지원
해결 방법:
1. 새 마이그레이션으로 롤백 (권장)
2. 수동 롤백 (긴급 상황)
5-2) 새 마이그레이션으로 롤백
V2__add_email_to_users.sql (원본):
ALTER TABLE users
ADD COLUMN email VARCHAR(255);
V3__remove_email_from_users.sql (롤백):
ALTER TABLE users
DROP COLUMN email;
5-3) 수동 롤백 (긴급)
-- 1. 문제가 된 마이그레이션 확인
SELECT * FROM flyway_schema_history
WHERE success = 0;
-- 2. 수동으로 롤백 SQL 실행
ALTER TABLE users
DROP COLUMN email;
-- 3. flyway_schema_history에서 해당 레코드 삭제 (주의!)
DELETE FROM flyway_schema_history
WHERE version = '2';
-- 4. 마이그레이션 파일 수정 후 재실행
6) 실무 패턴
6-1) 환경별 설정
개발 환경:
# application-dev.yml
spring:
flyway:
clean-disabled: false # clean 허용 (전체 삭제)
baseline-on-migrate: true
운영 환경:
# application-prod.yml
spring:
flyway:
clean-disabled: true # clean 금지 (안전)
validate-on-migrate: true
out-of-order: false # 순서 엄격히 적용
6-2) 버전 번호 전략
방법 1: 연속 번호
V1__create_users.sql
V2__create_orders.sql
V3__add_email.sql
방법 2: 날짜 기반 (권장)
V20251216_1000__create_users.sql
V20251216_1030__create_orders.sql
V20251217_0900__add_email.sql
장점:
- 여러 개발자가 동시 작업 시 충돌 감소
- 시간 순서 명확
방법 3: 시맨틱 버저닝
V1.0__initial_schema.sql
V1.1__add_users_email.sql
V2.0__refactor_orders.sql
장점:
- 메이저/마이너 구분
- 하위 호환성 표시
6-3) 테스트 데이터
src/main/resources/db/migration/
├── V1__create_schema.sql
├── V2__add_columns.sql
└── ...
src/test/resources/db/migration/
├── V1__create_schema.sql (동일)
├── V2__add_columns.sql (동일)
└── V999__test_data.sql (테스트 데이터)
V999__test_data.sql:
-- 테스트 데이터 (테스트 환경에서만)
INSERT INTO users (username, email) VALUES
('alice', 'alice@test.com'),
('bob', 'bob@test.com');
INSERT INTO orders (user_id, amount, status) VALUES
(1, 100.00, 'COMPLETED'),
(2, 200.00, 'PENDING');
7) 베스트 프랙티스
✅ 1. 작은 단위로 마이그레이션
❌ 나쁜 예: 하나의 파일에 모든 변경
V1__massive_changes.sql
- 10개 테이블 생성
- 100개 컬럼 추가
- 데이터 마이그레이션
✅ 좋은 예: 논리적 단위로 분리
V1__create_users_table.sql
V2__create_orders_table.sql
V3__add_email_to_users.sql
✅ 2. 실행 전 백업
# 운영 DB 마이그레이션 전 백업
mysqldump -u user -p mydb > backup_before_migration.sql
# 마이그레이션 실행
./gradlew bootRun
# 문제 발생 시 복구
mysql -u user -p mydb < backup_before_migration.sql
✅ 3. 멱등성 (Idempotent) 고려
-- ❌ 나쁜 예 (재실행 시 에러)
CREATE TABLE users (...);
-- ✅ 좋은 예 (재실행 가능)
CREATE TABLE IF NOT EXISTS users (...);
-- 컬럼 추가 (안전)
ALTER TABLE users
ADD COLUMN IF NOT EXISTS email VARCHAR(255);
-- 인덱스 추가 (안전)
CREATE INDEX IF NOT EXISTS idx_email ON users(email);
✅ 4. 트랜잭션 고려
-- DDL은 자동 커밋됨 (MySQL)
-- 여러 DDL을 하나의 트랜잭션으로 묶을 수 없음
-- ✅ 하나의 파일에 관련 작업만
V2__add_email.sql:
ALTER TABLE users ADD COLUMN email VARCHAR(255);
CREATE INDEX idx_email ON users(email);
✅ 5. 변경 사항 문서화
-- V5__add_user_status.sql
/**
* [JIRA-123] 사용자 상태 관리 기능 추가
*
* 변경 내용:
* - users 테이블에 status 컬럼 추가
* - 기존 deleted_at, email_verified 컬럼을 status로 통합
*
* 작성자: Alice
* 작성일: 2025-12-16
*/
ALTER TABLE users
ADD COLUMN status VARCHAR(20);
-- 기존 데이터 마이그레이션
UPDATE users ...
8) 장애 대응
8-1) 마이그레이션 실패 시
문제:
- V5 마이그레이션 중 에러 발생
- flyway_schema_history에 실패 기록
- 다음 실행 시 V5를 건너뜀
해결:
1. 문제 파악
SELECT * FROM flyway_schema_history WHERE success = 0;
2. 수동 롤백 (DB 상태 복구)
3. flyway_schema_history에서 실패 레코드 삭제
DELETE FROM flyway_schema_history WHERE version = '5';
4. 마이그레이션 파일 수정
5. 재실행
8-2) Checksum 불일치
문제:
- 이미 실행된 마이그레이션 파일 수정
- Checksum 불일치 에러
해결 (주의!):
1. 파일을 원래대로 되돌리기 (권장)
2. 또는 Flyway repair (검증 후 사용)
flyway repair
3. 또는 새 마이그레이션 생성 (가장 안전)
V6__fix_previous_migration.sql
9) 자주 하는 실수
❌ 실수 1: 실행된 파일 수정
❌ 나쁜 예:
V1__create_users.sql 실행 후 내용 수정
→ Checksum 불일치 에러
✅ 좋은 예:
새 마이그레이션 파일 생성
V2__add_missing_column.sql
❌ 실수 2: 버전 번호 중복
❌ 나쁜 예:
개발자 A: V3__add_email.sql
개발자 B: V3__add_phone.sql
→ 충돌!
✅ 좋은 예:
날짜 기반 버전 사용
V20251216_1000__add_email.sql
V20251216_1030__add_phone.sql
❌ 실수 3: 롤백 계획 없음
❌ 나쁜 예:
마이그레이션만 작성, 롤백 고려 안 함
✅ 좋은 예:
마이그레이션 작성 시 롤백 방법 문서화
또는 새 마이그레이션으로 롤백 준비
연습 (추천)
Flyway 설정
- Spring Boot 프로젝트에 Flyway 추가
- 테이블 생성 마이그레이션 작성
버전 관리
- 여러 마이그레이션 파일 작성
- 실행 이력 확인
롤백 연습
- 컬럼 추가 후 제거
- 새 마이그레이션으로 롤백
요약: 스스로 점검할 것
- DB 마이그레이션의 필요성을 설명할 수 있다
- Flyway로 스키마 버전을 관리할 수 있다
- 마이그레이션 파일 명명 규칙을 이해한다
- 안전한 마이그레이션 전략을 수립할 수 있다
- 롤백 방법을 알고 있다
다음 단계
- 데이터베이스 샤딩:
/learning/deep-dive/deep-dive-database-sharding/ - 읽기 전용 복제본:
/learning/deep-dive/deep-dive-database-replication/ - 무중단 배포:
/learning/deep-dive/deep-dive-zero-downtime-deployment/
💬 댓글