Flyway DB 마이그레이션 관리: ALTER TABLE 한 줄이 서비스를 멈추는 이유

📖 13min read

운영 DB에 ALTER TABLE을 날렸다

금요일 오후 4시. 담당자의 요청으로 프로젝트 테이블에 컬럼 하나를 추가해야 했다. 간단한 작업이었다.

ALTER TABLE project ADD COLUMN priority VARCHAR(20);

개발 서버에서 테스트하고, 운영 서버에 접속해서 같은 SQL을 실행했다. 문제 없었다. 배포하고 퇴근했다.

월요일 아침, 전화가 울렸다.

“주말에 배포한 건데, 개발 서버에서는 되는데 운영 서버에서 에러가 나요.”

확인해 보니, 다른 개발자가 금요일 저녁에 같은 테이블에 다른 컬럼을 추가하는 SQL을 운영 DB에 직접 실행했다. 순서가 꼬여서 애플리케이션이 기대하는 스키마와 실제 DB 스키마가 달라져 있었다. 어떤 SQL이 언제 실행됐는지 기록이 없어서, 개발 서버와 운영 서버의 스키마를 일일이 비교해야 했다.

‘DB 스키마 변경을 수동으로 관리하면 안 된다.’ 이 교훈을 얻기까지 반나절을 날렸다.

두 개발자가 동시에 운영 DB를 수동으로 수정하면, 아무도 전체 그림을 모르게 된다.

DB 마이그레이션이란: 왜 수동 SQL이 위험한가

DB 마이그레이션(Database Migration)이란, 데이터베이스 스키마의 변경 이력을 코드로 관리하는 것이다. 마치 Git이 소스 코드의 변경 이력을 추적하듯, DB 마이그레이션 도구는 테이블 구조의 변경 이력을 추적한다.

수동 SQL이 위험한 이유를 정리하면 이렇다.

수동 SQL 관리마이그레이션 도구
누가 언제 어떤 SQL을 실행했는지 모름모든 변경이 파일로 기록됨
개발/운영 서버 스키마가 다를 수 있음모든 환경에 같은 순서로 적용
같은 SQL을 중복 실행할 수 있음이미 실행된 것은 건너뜀
실수하면 되돌리기 어려움이력이 있어서 문제 추적 가능
새 팀원이 DB를 처음부터 구축할 수 없음마이그레이션 파일만 실행하면 재현

핵심은 ‘스키마 변경을 코드화하고, 버전을 관리한다’는 것이다.


Flyway 시작하기

Flyway는 가장 널리 쓰이는 Java/Spring 생태계의 DB 마이그레이션 도구다. SQL 파일 기반이라, 새로운 문법을 배울 필요 없이 그냥 SQL을 쓰면 된다.

의존성 추가

// build.gradle
implementation 'org.flywaydb:flyway-core'

Spring Boot에서는 이 의존성만 추가하면, 애플리케이션 시작 시 자동으로 마이그레이션이 실행된다.

설정

# application.yml
spring:
  flyway:
    enabled: true
    locations: classpath:db/migration
    baseline-on-migrate: true    # 기존 DB에 Flyway를 처음 적용할 때

baseline-on-migrate: true는 이미 테이블이 존재하는 운영 DB에 Flyway를 처음 도입할 때 필요하다. 기존 스키마를 “버전 1″로 간주하고, 이후 변경부터 추적한다.

파일 명명 규칙

마이그레이션 파일은 src/main/resources/db/migration/ 폴더에 넣는다. 파일 이름에 규칙이 있다.

V1__create_project_table.sql
V2__add_priority_column.sql
V3__create_member_table.sql
V3.1__add_member_email_index.sql
구성 요소의미예시
VVersioned migration (버전 마이그레이션)V
1, 2, 3버전 번호 (순서대로 실행)V2
__ (언더스코어 2개)구분자V2__
설명이 마이그레이션이 뭘 하는지add_priority_column
.sqlSQL 파일.sql

Flyway는 버전 번호 순서대로 마이그레이션을 실행한다. 이미 실행된 버전은 건너뛴다. flyway_schema_history 테이블에 실행 이력이 기록된다.

첫 마이그레이션

-- V1__create_project_table.sql
CREATE TABLE project (
    id BIGSERIAL PRIMARY KEY,
    name VARCHAR(200) NOT NULL,
    status VARCHAR(50) NOT NULL DEFAULT 'ACTIVE',
    region VARCHAR(100),
    category VARCHAR(100),
    manager_name VARCHAR(100),
    start_date DATE,
    end_date DATE,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

애플리케이션을 시작하면 Flyway가 이 파일을 감지하고 자동 실행한다. 로그에서 확인할 수 있다.

Flyway Community Edition
Successfully applied 1 migration to schema "public" (execution time 00:00.032s)
Flyway는 마이그레이션 파일을 버전 순서대로 실행하고, 이력을 기록한다.

실전 패턴

컬럼 추가

가장 흔한 마이그레이션이다. 서론에서 문제를 일으켰던 바로 그 작업.

-- V2__add_priority_to_project.sql
ALTER TABLE project ADD COLUMN priority VARCHAR(20) DEFAULT 'NORMAL';

기존 데이터 변환

컬럼을 추가한 뒤, 기존 데이터를 채워야 하는 경우도 많다.

-- V3__populate_priority_from_category.sql
UPDATE project SET priority = 'HIGH' WHERE category = '긴급';
UPDATE project SET priority = 'NORMAL' WHERE priority IS NULL;

스키마 변경과 데이터 변환은 별도 파일로 분리하는 것이 좋다. 한 파일에 섞으면 어디서 문제가 생겼는지 추적하기 어렵다.

인덱스 추가

-- V4__add_index_project_status.sql
CREATE INDEX idx_project_status ON project(status);
CREATE INDEX idx_project_region ON project(region);

배포와 마이그레이션 순서

실무에서 중요한 것은 ‘배포 순서’다. 새 컬럼을 쓰는 코드를 먼저 배포하면, 아직 컬럼이 없는 DB에서 에러가 난다.

안전한 순서:

  1. 마이그레이션 실행 (컬럼 추가) -> 코드 배포 (컬럼 사용)
  2. 또는: 컬럼 추가 + 기본값 설정 -> 코드 배포 -> 데이터 채우기

위험한 순서:

  1. 코드 배포 (컬럼 사용) -> 마이그레이션 실행 (컬럼 추가) -> 에러!

Spring Boot + Flyway에서는 애플리케이션 시작 시 마이그레이션이 먼저 실행되므로, 같은 배포에 포함시키면 자연스럽게 순서가 맞는다. 하지만 무중단 배포(롤링 배포)에서는 구 버전 코드와 신 버전 스키마가 공존하는 순간이 있으므로, 새 컬럼에 반드시 기본값이나 NULL 허용을 설정해야 한다.


롤백보다 중요한 것: Forward Fix

“마이그레이션을 롤백할 수 있나요?”

이 질문은 자주 나오지만, 현실은 이렇다. ‘DB 마이그레이션은 앱 코드처럼 간단히 롤백되지 않는다.’

코드는 이전 버전으로 되돌리면 끝이다. 하지만 DB는 다르다. 컬럼을 추가한 뒤 데이터가 들어갔으면, 컬럼을 삭제하면 데이터도 사라진다. 테이블 이름을 바꿨는데 다른 서비스가 기존 이름으로 접근하고 있었다면, 되돌려도 이미 에러가 발생한 뒤다.

그래서 실무에서는 롤백 스크립트를 미리 준비하기보다, ‘Forward Fix(앞으로 고치기)’ 전략을 쓴다.

전략설명
Forward Fix문제가 생기면 새 마이그레이션으로 수정 (V5로 V4의 실수를 고침)
호환성 유지구 버전 코드와 신 버전 스키마가 공존할 수 있게 설계
백업마이그레이션 전 DB 백업은 기본
작은 단위큰 변경을 한 번에 하지 말고, 작은 단위로 나눠서 적용
-- V4에서 실수로 컬럼 타입을 잘못 지정했다면
-- V4를 수정하지 말고 V5를 추가한다

-- V5__fix_priority_column_type.sql
ALTER TABLE project ALTER COLUMN priority TYPE VARCHAR(50);

Flyway는 이미 실행된 마이그레이션 파일을 수정하면 체크섬 오류가 발생한다. 실수를 고치려면 기존 파일을 수정하지 말고, 새 버전 파일을 추가해야 한다.


Flyway vs Liquibase

DB 마이그레이션 도구로 Liquibase도 많이 언급된다. 둘의 핵심 차이는 간단하다.

FlywayLiquibase
마이그레이션 형식SQL 파일XML/YAML/JSON/SQL
학습 곡선낮음 (SQL만 알면 됨)높음 (자체 문법)
DB 독립성SQL이 DB 종속적자체 형식이면 DB 독립적
롤백수동 (Forward Fix)자동 롤백 일부 지원
적합한 팀SQL에 익숙한 팀, 단일 DB여러 DB를 지원해야 하는 팀

PostgreSQL만 쓰고, SQL에 익숙한 팀이라면 Flyway가 더 직관적이다. “SQL 파일 하나 추가하면 끝”이라는 단순함이 Flyway의 최대 강점이다.


실무 조언

1. 기존 프로젝트에 Flyway 도입하기

이미 운영 중인 DB에 Flyway를 처음 적용하는 것이 가장 까다롭다. 현재 스키마를 V1으로 잡고, baseline-on-migrate: true를 설정한다.

-- V1__baseline.sql
-- 현재 운영 DB의 스키마를 그대로 기록 (실행은 안 되고 기록용)
-- pg_dump --schema-only 로 현재 스키마를 추출해서 넣는다

2. 팀 규칙

규칙이유
마이그레이션 파일은 한 번 커밋하면 수정하지 않는다체크섬 오류 방지
파일명은 설명적으로V7__add_index.sql보다 V7__add_index_project_status.sql
DDL과 DML은 파일 분리문제 추적 용이
운영 DB에 수동 SQL 금지Flyway가 관리하는 스키마와 불일치 방지
마이그레이션 전 백업기본 중의 기본

3. 개발 환경에서 유용한 설정

# application-local.yml
spring:
  flyway:
    clean-disabled: false   # 로컬에서만 clean 허용 (운영에서는 절대 금지)
  jpa:
    hibernate:
      ddl-auto: validate    # Flyway가 스키마 관리, Hibernate는 검증만

ddl-auto: validate는 중요하다. Hibernate가 스키마를 자동 생성/수정하지 않고, Flyway가 만든 스키마와 엔티티가 일치하는지만 검증한다. 불일치가 있으면 시작 시 에러가 나서 바로 알 수 있다.


마치며: 스키마 변경도 코드다

코드 변경은 Git으로 관리하면서, DB 변경은 수동으로 관리하는 팀이 아직도 많다. Flyway를 도입하면 DB 변경도 코드처럼 리뷰하고, 추적하고, 재현할 수 있다.

정리하면 이렇다.

  • 운영 DB에 수동 SQL을 직접 실행하지 마라.
  • 모든 스키마 변경은 마이그레이션 파일로 만들어라.
  • 이미 실행된 파일은 수정하지 말고, 새 파일로 고쳐라 (Forward Fix).
  • Flyway + Spring Boot는 의존성 하나 추가하면 바로 시작할 수 있다.

다음 글에서는 환경 분리와 Profile을 다룬다. 개발 서버에서는 되는데 운영 서버에서는 안 되는 이유가, 대부분 설정 파일이 달라서인 경우가 많다. application-local.yml, application-dev.yml, application-prod.yml을 어떻게 관리하는지 알아보겠다.

“Flyway DB 마이그레이션 관리: ALTER TABLE 한 줄이 서비스를 멈추는 이유”에 대한 1개의 생각

댓글 남기기