Flyway로 DB 스키마 관리하기
Flyway란
Flyway는 DB 스키마 변경을 SQL 파일로 버전 관리하는 마이그레이션 도구다. V1__init.sql, V2__add_tag.sql처럼 번호 붙은 SQL 파일을 순서대로 실행하고, 어디까지 실행했는지를 DB 안의 flyway_schema_history 테이블에 기록한다.
한 줄 요약: 스키마도 코드처럼 git으로 관리하고, 앱이 뜰 때 자동으로 최신 상태로 맞춘다.
왜 필요한가
수동 관리의 문제
| 방식 | 문제 |
|---|---|
개발자가 DB에 직접 ALTER TABLE |
누가 언제 뭘 바꿨는지 기록이 없다. 로컬·테스트·운영 DB가 서로 달라진다 |
JPA ddl-auto: update |
컬럼 추가는 해주지만 이름 변경·삭제·데이터 이전은 못 한다. 운영에서 쓰면 예측 불가 |
JPA ddl-auto: create |
뜰 때마다 테이블을 날린다. 운영에서는 절대 못 쓴다 |
Flyway를 쓰면
- 스키마 변경 이력이 git 커밋으로 남는다 (코드 리뷰도 가능)
- 새 DB(로컬, Testcontainers, 운영)가 같은 SQL을 같은 순서로 실행하므로 항상 같은 모양이 된다
- 이미 적용된 파일은 건너뛰고 새 파일만 실행한다
- 적용된 파일이 몰래 바뀌면 체크섬 불일치로 기동을 막아준다
Tip
JPA는 ddl-auto: validate로 두고 스키마 생성은 Flyway에 맡기는 조합이 정석이다. Flyway가 만들고, Hibernate는 엔티티와 테이블이 맞는지 검사만 한다. 안 맞으면 앱이 바로 죽어서 실수를 빨리 알 수 있다. 이유는 바로 아래 섹션에 자세히 정리했다.
Flyway와 ddl-auto: validate를 같이 쓰는 이유
핵심: 스키마의 주인은 하나여야 한다
JPA(Hibernate)도 엔티티를 보고 테이블을 만들거나 고칠 수 있다. Flyway까지 쓰면 스키마를 바꾸는 주체가 둘이 된다. 그래서 역할을 나눈다.
| 역할 | 담당 | 하는 일 |
|---|---|---|
| 스키마를 바꾼다 | Flyway | V 파일을 순서대로 실행 |
| 스키마를 검사한다 | Hibernate (validate) |
엔티티와 실제 테이블·컬럼이 맞는지 확인, 틀리면 기동 실패 |
ddl-auto 값별로 Flyway와 같이 쓰면
| 값 | Flyway와 함께 쓰면 | 판정 |
|---|---|---|
create / create-drop |
Flyway가 만든 테이블을 Hibernate가 지우고 다시 만든다. 데이터도, 히스토리와의 일치도 깨진다 | ❌ |
update |
Hibernate가 몰래 컬럼을 추가한다. 그 변경은 V 파일에 없으니 다른 환경(운영·동료 PC·CI)에는 반영되지 않는다. 나중에 같은 컬럼을 V 파일로 추가하면 "이미 있음"으로 실패하기도 한다 | ❌ |
none |
충돌은 없지만 엔티티와 테이블이 어긋나도 아무도 모른다. 해당 쿼리가 처음 실행될 때 런타임 SQL 오류로 터진다 | △ |
validate |
아무것도 바꾸지 않고, 어긋나면 기동 시점에 바로 실패 | ✅ |
Note
Spring Boot는 PostgreSQL 같은 외부 DB에서 ddl-auto 기본값이 none이다(H2 같은 내장 DB일 때만 create-drop). 즉 아무것도 안 적으면 검사 없이 넘어가므로 validate는 명시적으로 적어야 한다.
실행 순서가 보장되는가?
된다. Spring Boot는 Flyway 마이그레이션을 JPA(EntityManagerFactory) 초기화보다 먼저 실행하도록 의존 관계를 걸어 둔다. 그래서 Hibernate가 검사할 때는 이미 최신 스키마다.
sequenceDiagram
participant App as Spring Boot
participant FW as Flyway
participant DB as PostgreSQL
participant HB as Hibernate
App->>FW: 1. 마이그레이션 실행
FW->>DB: 안 된 V 파일 적용
App->>HB: 2. EntityManagerFactory 생성
HB->>DB: 테이블·컬럼 메타데이터 조회
alt 엔티티와 일치
HB-->>App: 기동 계속
else 불일치
HB-->>App: Schema-validation 예외 → 기동 실패
end
실제로 잡아주는 실수
엔티티에 필드만 추가하고 마이그레이션을 깜빡한 경우:
@Entity
class Note {
// ...
private String summary; // 추가했는데 V 파일은 안 만듦
}
Schema-validation: missing column [summary] in table [note]
V2__add_note_summary.sql을 만들면 해결된다. 이 오류가 배포 후 첫 요청이 아니라 로컬 기동이나 CI 테스트에서 나온다는 게 핵심이다. Testcontainers로 진짜 PostgreSQL을 띄우는 테스트라면 Flyway 실행 + validate 검사가 테스트마다 같이 돈다.
validate가 검사하지 않는 것
| 검사함 | 검사 안 함 |
|---|---|
| 테이블 존재 | 인덱스 |
| 컬럼 존재 | unique·FK·check 제약 |
| 컬럼 타입 호환 | DB에만 있는 남는 컬럼·테이블 |
Warning
validate를 통과했다고 스키마가 완벽한 건 아니다. 제약 조건과 인덱스는 V 파일 리뷰로 챙겨야 한다.
동작 방식
flowchart TD
A[앱 기동] --> B[Flyway 실행]
B --> C{flyway_schema_history 테이블 있음?}
C -- 없음 --> D[히스토리 테이블 생성]
C -- 있음 --> E[적용된 버전 · 체크섬 읽기]
D --> F[db/migration 폴더의 V 파일 스캔]
E --> F
F --> G{기존 파일 체크섬 일치?}
G -- 불일치 --> X[기동 실패]
G -- 일치 --> H[아직 안 된 버전만 순서대로 실행]
H --> I[히스토리에 기록]
I --> J[Hibernate validate → 앱 시작]
실제 설정 (Spring Boot + PostgreSQL)
1. 의존성
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-flyway'
implementation 'org.flywaydb:flyway-database-postgresql' // DB별 모듈 필요
runtimeOnly 'org.postgresql:postgresql'
testImplementation 'org.springframework.boot:spring-boot-starter-flyway-test'
testImplementation 'org.springframework.boot:spring-boot-testcontainers'
testImplementation 'org.testcontainers:testcontainers-postgresql'
}
Important
Flyway 10부터 PostgreSQL·MySQL 등은 코어에서 분리됐다. flyway-database-postgresql 같은 DB별 모듈을 따로 넣지 않으면 Unsupported Database 오류가 난다.
2. application.yml
spring:
jpa:
hibernate:
ddl-auto: validate # 스키마는 Flyway가, Hibernate는 검사만
Flyway 쪽 설정은 따로 안 해도 된다. 스타터가 있으면 기본값으로 classpath:db/migration을 읽어 기동 시 자동 실행한다.
3. 마이그레이션 파일
위치: src/main/resources/db/migration/
파일 이름 규칙:
V1__init.sql
│ │ └─ 설명 (밑줄은 공백으로 표시됨)
│ └─── 밑줄 두 개 (필수)
└───── V + 버전 번호
예시 V1__init.sql:
create table category (
id bigserial primary key,
slug text not null unique,
name text not null,
created_at timestamptz not null
);
create table note (
id bigserial primary key,
category_id bigint not null references category (id),
slug text not null unique,
title text not null,
body_md text not null,
created_at timestamptz not null,
updated_at timestamptz not null
);
4. 스키마를 바꿀 때
기존 파일은 건드리지 않고 새 버전 파일을 추가한다.
-- V2__add_note_summary.sql
alter table note add column summary text;
적용 이력 확인:
select version, description, success, installed_on
from flyway_schema_history
order by installed_rank;
지켜야 할 규칙
- 한 번 적용(머지)된 V 파일은 절대 수정하지 않는다 → 고칠 게 있으면 새 버전으로
- 버전 번호는 겹치지 않게 (브랜치 여러 개면 머지 전에 번호 확인)
- 엔티티를 바꿨으면 같은 커밋에 마이그레이션도 넣는다 (
validate가 잡아줌) - 테스트는 Testcontainers로 진짜 DB를 띄워 마이그레이션까지 같이 검증한다
Warning
컬럼 삭제·이름 변경은 되돌리기 어렵다. 운영 데이터가 있다면 "새 컬럼 추가 → 데이터 복사 → 코드 전환 → 옛 컬럼 삭제"처럼 여러 버전에 나눠 진행한다.
자주 만나는 문제와 해결
| 증상 | 원인 | 해결 |
|---|---|---|
Validate failed: Migration checksum mismatch |
이미 적용된 V 파일을 수정함 | 수정을 되돌리고 새 V 파일로 변경. 로컬 DB라면 DB를 지우고 다시 띄워도 됨 |
Unsupported Database: PostgreSQL |
DB별 모듈 누락 | flyway-database-postgresql 추가 |
Schema-validation: missing column |
엔티티만 바꾸고 마이그레이션 안 만듦 | 해당 컬럼을 추가하는 V 파일 작성 |
Found non-empty schema(s) ... but no schema history table |
기존 DB에 Flyway를 처음 붙임 | spring.flyway.baseline-on-migrate: true로 현재 상태를 기준선으로 등록 |
| 실패한 마이그레이션 후 계속 기동 실패 | 히스토리에 success=false 기록이 남음 |
SQL을 고친 뒤 flyway repair로 실패 기록 정리 (PostgreSQL은 DDL도 트랜잭션이라 보통 자동 롤백됨) |
Note
로컬 개발 중 아직 아무 데도 공유 안 한 마이그레이션이면 파일을 고치고 DB 볼륨을 지우는 게 가장 빠르다. 한 번이라도 다른 환경에 적용됐다면 무조건 새 버전 파일로 간다.
댓글 0
로그인하고 댓글 달기