[third tool] flyway의 도입기

junsung kim·2026년 3월 20일

[project]- thirdTool

목록 보기
21/27

Flyway를 도입할 수밖에 없었던 이유

"코드는 Git으로 관리하면서, 왜 DB 스키마는 기억에 의존하고 있었을까?"


시작은 편함이었다

Spring Boot 프로젝트를 처음 시작할 때, 대부분의 개발자는 application.yml에 이 한 줄을 아무 고민 없이 추가한다.

spring:
  jpa:
    hibernate:
      ddl-auto: create-drop

토이 프로젝트 수준에서는 이게 사실 최고의 선택처럼 느껴진다. 엔티티 클래스를 수정하면 테이블이 알아서 반영되고, 애플리케이션을 재시작하면 깨끗하게 초기화된다. 마이그레이션 스크립트를 따로 작성할 필요도 없고, DDL을 직접 손댈 일도 없다.

문제는, 이 편함이 "지금은 괜찮지만, 나중엔 반드시 터진다" 는 종류의 편함이라는 점이다.


ddl-auto가 만들어내는 진짜 문제

스키마 변경 이력이 없다

  • 이 문제점 느끼는데 꽤 오랜 시간이 걸렸다.

코드는 Git에 커밋이 쌓인다. 누가 언제 어떤 이유로 어떤 필드를 추가했는지, git log나 PR 히스토리를 보면 알 수 있다.

그런데 DB 스키마는? ddl-auto: update를 쓰고 있다면, 어느 시점에 어떤 컬럼이 생겼는지 알 방법이 없다. 테이블 구조 자체에는 아무런 기록이 남지 않는다.

이건 단순히 불편함의 문제가 아니다. "왜 이 컬럼이 생겼는가" 라는 질문에 대한 답이 코드베이스 어디에도 없다는 의미다.

환경 간 스키마 일관성을 보장할 수 없다

로컬에서는 잘 돌아가는데, 서버에 올리면 이상하다. 팀원 A의 로컬에서는 되는데, 팀원 B의 로컬에서는 안 된다.

ddl-auto: update는 현재 엔티티 상태를 기준으로 없는 컬럼만 추가한다. 컬럼을 지우거나, 타입을 바꾸거나, 제약조건을 변경하는 건 직접 처리해야 한다. 결국 "내 로컬 DB엔 언제 어떻게 만들어졌는지 모를 컬럼들"이 조용히 쌓여간다.

create-drop은 운영에서 쓸 수 없다

로컬에서 create-drop을 쓰다가, 운영 환경으로 가면? 당연히 쓸 수 없다. 배포할 때마다 테이블이 날아간다.

그러면 운영 환경에서는 validatenone으로 설정하고, 스키마 변경은 직접 SQL을 손으로 실행하게 된다. 이 시점부터 스키마 변경은 완전히 수동 프로세스가 된다. 누군가 까먹으면, 배포가 터진다.


Flyway는 "스키마를 위한 Git"이다

Flyway의 핵심 개념은 단순하다.

모든 스키마 변경은 SQL 파일로 기록되고, 한 번 적용된 파일은 절대 수정하지 않는다.

프로젝트에 src/main/resources/db/migration/ 디렉토리를 만들고, 다음과 같은 네이밍 컨벤션으로 파일을 추가하기만 하면 된다.

V1__init_schema.sql
V2__add_card_table.sql
V3__add_summary_column.sql

애플리케이션이 시작될 때 Flyway는 flyway_schema_history 테이블을 확인하고, 아직 적용되지 않은 마이그레이션 파일만 순서대로 실행한다.

이게 전부다. 그런데 이 단순한 구조가 앞서 말한 문제들을 모두 해결한다.


도입하면 생기는 것들

1. 스키마 변경 이력이 생긴다

V3__add_summary_column.sql 파일이 Git 히스토리에 남는다. 언제, 누가, 왜 이 컬럼을 추가했는지 커밋 메시지와 함께 추적할 수 있다. DB 변경사항이 코드 변경사항과 같은 단위로 관리되기 시작한다.

2. 환경 간 스키마가 동일함을 보장한다

새로운 팀원이 합류해서 프로젝트를 클론하면, 애플리케이션을 실행하는 것만으로 DB 스키마가 최신 상태로 자동 세팅된다. 로컬, 개발서버, 운영서버 모두 동일한 마이그레이션 파일을 순서대로 적용받는다.

3. ddl-auto: validate를 운영에서 안심하고 쓸 수 있다

Flyway를 도입하면 운영 환경 설정을 아래처럼 가져갈 수 있다.

# 운영 환경
spring:
  jpa:
    hibernate:
      ddl-auto: validate  # 엔티티와 스키마가 불일치하면 시작 자체를 막는다
  flyway:
    enabled: true

validate 옵션은 Hibernate가 현재 엔티티 클래스와 실제 DB 스키마가 일치하는지 검증하고, 맞지 않으면 애플리케이션 시작을 실패시킨다. Flyway와 함께 쓰면, 마이그레이션 파일을 빠뜨린 채 배포했을 때 조용히 넘어가지 않고 명시적으로 터진다. "배포 후 런타임에 터지는 것"보다 "시작 시점에 터지는 것"이 훨씬 낫다.

4. DDL을 직접 통제하게 된다

ddl-auto에 의존하는 동안은 사실 Hibernate가 DDL을 대신 만들어주는 것이다. 컬럼 타입, 인덱스, 제약조건... Hibernate가 엔티티를 보고 나름대로 판단해서 생성한다.

Flyway를 도입하면 SQL을 직접 작성하게 된다. 처음엔 번거롭게 느껴지지만, 덕분에 "내 DB 스키마가 정확히 어떻게 생겼는지" 를 명확히 알게 된다. VARCHAR(255)로 뭉뚱그려 생성되던 컬럼을 VARCHAR(500) NOT NULL로 정확히 지정할 수 있고, 복합 인덱스를 언제 어떤 이유로 추가했는지 SQL 파일과 커밋 메시지에 남길 수 있다.


환경별 설정 분리 전략

Flyway를 도입할 때 한 가지 짚어두어야 할 게 있다. 테스트 환경, 특히 @DataJpaTest 같은 슬라이스 테스트에서는 Flyway와 H2를 함께 쓰면 MySQL 전용 SQL 문법 충돌이 발생한다.

내가 택한 방식은 간단하다.

# 로컬/운영 (application.yml)
spring:
  jpa:
    hibernate:
      ddl-auto: validate
  flyway:
    enabled: true

# 테스트 (application-test.yml)
spring:
  jpa:
    hibernate:
      ddl-auto: create-drop  # H2가 엔티티 기반으로 스키마를 직접 생성
  flyway:
    enabled: false           # Flyway 비활성화로 MySQL 문법 충돌 방지

테스트는 H2가 알아서 스키마를 만들게 두고, Flyway는 실제 MySQL을 쓰는 환경에서만 동작하게 분리한다.


마치며

Flyway 도입이 귀찮게 느껴지는 건, SQL 파일을 직접 작성해야 하기 때문이다. 하지만 그 귀찮음은 사실 "지금까지 Hibernate에게 맡겨두고 신경 쓰지 않았던 것" 을 직접 챙기기 시작하는 과정이다.

코드 변경은 커밋으로 남긴다. 그렇다면 스키마 변경도 마찬가지여야 한다.

결국 Flyway는 선택의 문제가 아니라, 프로젝트가 토이 수준을 넘어서는 순간 자연스럽게 도달하게 되는 결론이다.

profile
edit하는 개발자! story 있는 삶

0개의 댓글