Spring Boot Flyway & Liquibase — DB 마이그레이션 자동화

Spring Boot에서 데이터베이스 스키마를 버전 관리하는 방법을 다룹니다. Flyway의 네이밍 규칙부터 실행 흐름, Liquibase의 changelog 기반 관리까지 두 도구의 차이와 Spring Boot 통합 방법을 설명합니다.

· 8 min read · PALDYN Team

지난 글에서 복수의 DataSource를 구성하는 방법을 살펴봤습니다. 여러 환경(로컬·스테이징·프로덕션)을 운영하다 보면 각 환경의 DB 스키마가 조금씩 달라지는 문제가 생깁니다. 그 해결책이 DB 마이그레이션 도구입니다. 스프링 부트는 Flyway와 Liquibase 두 가지를 공식 지원하며, 의존성 하나만 추가하면 앱 기동 시 자동으로 스키마를 최신 상태로 맞춥니다.

DB 마이그레이션이 필요한 이유

코드는 Git으로 버전 관리를 하지만, DB 스키마는 보통 수동 ALTER TABLE로 변경합니다. 이 방식은 세 가지 문제를 낳습니다.

  • 개발자마다 로컬 DB 상태가 달라 “내 PC에서는 됩니다” 상황 발생
  • 배포 순서가 꼬이면 앱과 스키마 버전이 어긋남
  • 어떤 순서로 어떤 DDL을 적용했는지 추적 불가

마이그레이션 도구는 스크립트를 버전 번호에 묶고, 히스토리 테이블에 실행 기록을 남깁니다. 동일한 스크립트는 한 번만 실행되므로 어느 환경에서든 재현 가능한 스키마를 보장합니다.

Flyway 기초

의존성

<!-- Maven -->
<dependency>
  <groupId>org.flywaydb</groupId>
  <artifactId>flyway-core</artifactId>
</dependency>
<!-- MySQL 8+ 사용 시 추가 필요 -->
<dependency>
  <groupId>org.flywaydb</groupId>
  <artifactId>flyway-mysql</artifactId>
</dependency>
// Gradle
implementation 'org.flywaydb:flyway-core'
// MySQL 8+ 사용 시
implementation 'org.flywaydb:flyway-mysql'

spring-boot-starter-data-jpaflyway-core만 있으면 Spring Boot Auto-configuration이 나머지를 처리합니다.

네이밍 규칙

Flyway 스크립트는 파일명이 규칙을 결정합니다.

V{버전}__{설명}.sql
 │        │
 │        └── 언더스코어 두 개 (__)
 └── 버전 번호: 1, 2, 1.1, 202412010 등

예) V1__create_users.sql
    V2__add_email_column.sql
    V3__create_orders.sql

기본 경로는 src/main/resources/db/migration/입니다. 이 경로는 spring.flyway.locations로 변경할 수 있습니다.

실행 흐름

Flyway 마이그레이션 실행 흐름

앱이 기동되면 Flyway가 flyway_schema_history 테이블을 확인합니다. 테이블이 없으면 생성하고, 스크립트 디렉터리를 스캔해 아직 적용되지 않은 스크립트를 번호 순서대로 실행합니다. 실행 성공 시 success=true로 기록하며, 체크섬이 달라진 스크립트가 있으면 FlywayException을 던지고 기동을 중단합니다.

이 동작이 프로덕션 안전망이 됩니다. 스크립트 오류가 있으면 앱 자체가 뜨지 않으므로 잘못된 스키마로 서비스가 운영되는 상황을 방지합니다.

기본 설정

spring:
  flyway:
    enabled: true
    locations: classpath:db/migration
    baseline-on-migrate: true   # 기존 DB에 처음 적용 시 기준점 생성
    validate-on-migrate: true   # 체크섬 검증 (기본값 true)
    out-of-order: false         # 순서 역전 스크립트 허용 여부
    placeholders:
      schema: myapp

baseline-on-migrate: true는 이미 운영 중인 DB에 Flyway를 처음 도입할 때 필수입니다. V1부터 시작하는 히스토리 베이스라인을 설정해 기존 테이블과 충돌을 방지합니다.

실용 스크립트 예시

-- V1__create_users.sql
CREATE TABLE users (
    id      BIGINT PRIMARY KEY AUTO_INCREMENT,
    email   VARCHAR(255) NOT NULL UNIQUE,
    name    VARCHAR(100) NOT NULL,
    created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);

-- V2__add_role_column.sql
ALTER TABLE users ADD COLUMN role VARCHAR(20) NOT NULL DEFAULT 'USER';

-- V3__create_orders.sql
CREATE TABLE orders (
    id         BIGINT PRIMARY KEY AUTO_INCREMENT,
    user_id    BIGINT NOT NULL,
    total      DECIMAL(10,2) NOT NULL,
    status     VARCHAR(20) NOT NULL,
    created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    CONSTRAINT fk_orders_user FOREIGN KEY (user_id) REFERENCES users(id)
);

INSERT·UPDATE 같은 DML도 마이그레이션 스크립트에 넣을 수 있습니다. 초기 코드 테이블 값이나 기존 데이터 정제 작업에 활용합니다.

반복 스크립트 (Repeatable Migration)

버전 없이 R__ 접두사를 사용하면 체크섬이 바뀔 때마다 재실행됩니다. 뷰·프로시저처럼 자주 교체하는 객체에 적합합니다.

R__create_views.sql          ← 변경될 때마다 재실행
R__insert_code_data.sql      ← 코드 테이블 초기화

Liquibase 기초

Liquibase는 XML·YAML·JSON·SQL 등 다양한 형식의 changelog 파일로 변경을 기술합니다.

의존성

<dependency>
  <groupId>org.liquibase</groupId>
  <artifactId>liquibase-core</artifactId>
</dependency>

Changelog 작성 (XML)

<!-- db/changelog/db.changelog-master.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog
    xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
    http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.20.xsd">

  <changeSet id="1" author="dev">
    <createTable tableName="users">
      <column name="id" type="BIGINT" autoIncrement="true">
        <constraints primaryKey="true"/>
      </column>
      <column name="email" type="VARCHAR(255)">
        <constraints nullable="false" unique="true"/>
      </column>
    </createTable>
  </changeSet>

  <changeSet id="2" author="dev">
    <addColumn tableName="users">
      <column name="role" type="VARCHAR(20)"
              defaultValue="USER">
        <constraints nullable="false"/>
      </column>
    </addColumn>
    <rollback>
      <dropColumn tableName="users" columnName="role"/>
    </rollback>
  </changeSet>

</databaseChangeLog>

<rollback> 태그가 Flyway와 다른 결정적 차이입니다. rollback 명령어 하나로 changeSet을 역순으로 되돌릴 수 있습니다.

YAML Changelog (가독성 중시)

# db/changelog/001-create-users.yaml
databaseChangeLog:
  - changeSet:
      id: 1
      author: dev
      changes:
        - createTable:
            tableName: users
            columns:
              - column:
                  name: id
                  type: BIGINT
                  autoIncrement: true
                  constraints:
                    primaryKey: true
              - column:
                  name: email
                  type: VARCHAR(255)
                  constraints:
                    nullable: false
                    unique: true

설정

spring:
  liquibase:
    change-log: classpath:db/changelog/db.changelog-master.xml
    enabled: true
    contexts: dev    # 특정 context의 changeSet만 실행
    default-schema: myapp

Flyway vs Liquibase 비교

Flyway vs Liquibase 핵심 비교

두 도구 모두 Spring Boot Auto-configuration으로 설정이 거의 없어도 동작합니다. 단순함과 SQL 친화성을 원한다면 Flyway, XML/YAML 기반 추상화와 자동 롤백이 필요하다면 Liquibase를 선택합니다. 대부분의 스프링 부트 프로젝트에서는 Flyway가 기본 선택지입니다.

프로덕션 운영 팁

이미 서비스 중인 DB에 Flyway를 도입하는 절차입니다.

# 1. 현재 스키마를 V1__baseline.sql에 dump
mysqldump --no-data mydb > src/main/resources/db/migration/V1__baseline.sql

# 2. application.yml에 baseline 설정
# spring.flyway.baseline-on-migrate: true
# spring.flyway.baseline-version: 1

# 3. 이후 모든 스키마 변경은 V2__, V3__ 스크립트로 관리

절대 하면 안 되는 것: 적용된 마이그레이션 파일의 내용 수정. 체크섬이 달라져 다음 기동 시 FlywayException: Detected failed migration이 발생합니다. 수정이 필요하면 새 버전 스크립트를 추가합니다.

테스트 환경 설정

# src/test/resources/application-test.yml
spring:
  flyway:
    locations:
      - classpath:db/migration
      - classpath:db/testdata   # 테스트용 DML 스크립트 별도 관리
  datasource:
    url: jdbc:h2:mem:testdb;MODE=MySQL

테스트용 INSERT 스크립트는 R__test_data.sql(반복 실행형)로 관리하면 매 테스트마다 고정 데이터를 보장할 수 있습니다.


지난 글: Spring Boot 멀티 DataSource — 읽기/쓰기 분리부터 AbstractRoutingDataSource까지

다음 글: Spring Boot H2 콘솔 — 인메모리 DB로 빠른 개발 환경 구성


읽어주셔서 감사합니다. 😊