IT/서버

Diary 프로젝트 일기 (4) - Flyway 도입

altair823 2025. 5. 4. 03:24

서론

 서비스를 개발하다보면 DB의 스키마를 변경해야 할 일이 생긴다. 특히 프로젝트 초기일수록 더 그렇다. 많은 가이드에서 프로젝트를 시작할 때 RDB보다 MongoDB 같은 NoSQL을 추천하는 이유는 스키마 변경에 있다고 생각한다. 데이터 정합성보다 쉬운 스키마 변경이 더 중요하다면 애플리케이션 단에서 데이터 어노멀리를 처리할 수 있는 NoSQL이 더 효율적일 것이다. 

 아이러니하게도 나 같이 RDB에 더 익숙한 초보 개발자는 NoSQL보다 RDB로 시작하는게 더 생산성이 올라가곤 한다. 대부분의 데이터는 정형화된 데이터로 나타낼 수 있고, 높은 부하의 요청은 들어오지도 않고, 작은 서버에서 처리하지도 못하기 때문이다. 

 

스키마 변경과 불일치 문제

 Spring Boot에서 RDB를 쓰는 경우 중요한 문제가 있다. Entity와 DB의 스키마가 다를 때 Spring Boot는 이를 어떻게 처리할지 선택하는 옵션을 제공한다. spring.jpa.hibernate.ddl-auto가 바로 그것이다. 많은 튜토리얼에서 이를 create나 update로 사용하지만 운영 서버의 경우 절대 그렇게 할 수 없다. create는 기존 테이블들을 모두 드랍할 것이고, update는 테이블의 변경을 예측하기 어려울 뿐더러 원하는대로 바뀌지 않을 가능성이 있다. 

 그래서 보통 해당 옵션을 validate나 none으로 설정한다. validate는 DB의 스키마가 Entity와 다르면 애플리케이션 실행이 실패하며 none은 아무 일도 하지 않다가 스키마가 다르면 런타임에 실패한다. 나의 경우 개발 서버는 validate, 운영 서버는 none으로 설정되어 있다. 

 이번 문제는 여기서 비롯된다. 애플리케이션 단에서 새 Entity를 추가하고 기존 Entity에 컬럼을 추가한 일이 있었다. 그러나 validate였던 개발서버는 스키마의 불일치를 감지하고 실행을 중단시켰다. 

 DB에 붙어서 ALTER를 할 수도 있었지만 앞으로 서비스를 개발하면서 더 많은 스키마 변경이 있을 것이란 문제가 있었다. 그 모든 변경마다 DB에 직접 붙어서 반영해야 할 뿐만 아니라 개발 서버와 운영 서버 모두 변경해야 하니 일이 많아질 것 같았다. 때문에 다른 방법을 찾아보기로 했다. 

 

DDL 버저닝

 소프트웨어 마에스트로를 하면서 같은 문제를 멘토님께 물어본 적이 있다. 당시에는 개발 서버의 ddl-auto 설정이 create였고 운영서버는 update였다. 지금에서야 미친 짓인걸 알지만...ㅎ

 멘토님은 당연히 프로덕션 환경에서는 none 같이 DB를 바꾸지 않는 설정을 써야한다고 하셨고, 그 경우 DB를 손수 바꾸거나 DDL을 직접 쓰고 이를 버전으로 관리하는 방법이 있다고 말씀하셨다. 

 당시에는 개발 일정이 매우 촉박했기에 더 깊이 들어가지 못했으나, DDL로 DB 스키마를 직접 구성하고 이를 버전으로 관리할 수 있다는 기억만 남았다. 그러다 이번에 같은 문제에 봉착하게 되어 그 기억이 되살아났다. 

 찾아보니 Flyway라는 데이터베이스 마이그레이션 툴을 많이 쓰는 모양이었다. 자료도 많고 해서 Flyway를 도입하기로 했다. 

 

Flyway 도입

 Flyway를 적용하는 법은 간단하다. 데이터베이스에 맞는 딜드 의존성을 설정하고 스프링 yaml 파일을 수정하면 된다. 

 나의 경우 MySQL 8.0.41을 쓰고 있기 때문에 다음과 같이 의존성을 추가했다. 

implementation("org.flywaydb:flyway-mysql")

 

Flyway 설정

 그 후 다음과 같이 application-development.yaml파일을 수정했다. 민감한 정보는 jasypt를 사용해 암호화하였다. 

spring:
  flyway:
    enabled: true
    locations: classpath:db/migration/dev
    baseline-on-migrate: true
    baseline-version: 1.0.0
    url: ENC(****)
    user: ENC(****)
    password: ENC(****)
  jpa:
    hibernate:
      ddl-auto: validate
    properties:
      hibernate:
        dialect: org.hibernate.dialect.MySQLDialect
        format_sql: true
        use_sql_comments: true
        jdbc:
        batch_size: 100
        order_inserts: true
        show_sql: true
  datasource:
    url: ENC(****)
    username: ENC(****)
    password: ENC(****)

 여기서 주의할 점은 Flyway는 데이터베이스 스키마를 수정하는 역할을 하고 datasource의 연결 정보는 애플리케이션이 DB를 사용할 수 있도록 연결해주는 것이기 때문에 두 곳 모두 연결 정보를 적어줘야 한다. 만약 스키마를 변경할 DB와 데이터를 읽어올 DB가 다르다면 두 정보가 달라질 수도 있다. 

 그럼 이제 flyway의 설정들을 살펴보자. 

  • location: DDL들이 저장된 SQL 파일이 저장된 위치다. main/java/resource 아래에서부터 저장된다. 기본값은 db/migration이지만 나는 개발 서버와 운영 서버를 나누어 사용하고 있기 때문에 혹시 몰라서 dev 폴더 아래에 두었다. 
  • baseline-on-migrate: 기존 DB 스키마를 baseline-version 버전으로 간주하고 그 위에서부터 사용하겠다는 뜻이다. 또한 버전 baseline-version 의 DDL은 적용되었다고 간주한다. 따라서 baseline-version 버전의 DDL을 만들어 저장했다면 이는 적용되지 않는다. 기존 DB의 스키마가 해당 파일을 적용한 것으로 인식한다. 
  • baseline-version: 위에서 언급하였던 baseline-version 값이다. 나는 이를 1.0.0으로 설정했으니 기존 DB 스키마는 1.0.0의 DDL을 적용한 것으로 간주한다. 

 나는 기존 스키마를 그대로 사용하면서 그 위에 변경사항을 적용할 것이므로 버전 1.0.1의 DDL부터 저장하면 된다.

DDL 파일명

 Flyway는 이 DDL 파일의 파일명 또한 규칙을 둔다. 

 맨 앞 이미지를 보면 알 수 있다시피 대문자 V 뒤에 바로 버전이 있어야 하고, 그 뒤로 언더바 두 개, 설명, sql 확장자가 붙는다. 따라서 이런 형식을 사용해야 한다. 

  • V2__add_new_table.sql
  • V2_1__add_new_table.sql
  • V2_1_13__add_new_table.sql

버전의 숫자들은 언더바로 구분되며 꼭 시멘틱 버저닝일 필요는 없지만 나는 그렇게 사용했다. 

 

DDL 버전을 Flyway가 관리하는 방법

 그렇다면 Flyway는 현재 스키마가 어떤 버전인지 어떻게 알 수 있을까? 특정 데이터베이스만의 기능으로 이를 저장할 수도 있겠지만 다양한 데이터베이스를 지원해야하는 Flyway는 그냥 따로 테이블을 만들어 관리한다. 

 

Videos and top tips from the experts - Redgate University

New report 2025 State of the Database Landscape The 2025 State of the Database Landscape Report sheds light on the current state of database management and offers valuable insights into how organizations can navigate and simplify the growing complexities.

www.red-gate.com

 위 문서는 Flyway가 어떻게 버전 정보를 저장하는지 보여준다. 

 나도 직접 DB에 붙어서 그런 테이블이 있는지 확인해봤다. 

 1.0.0을 baseline으로 저장하고 이번에 새로 적용된 1.0.1을 테이블에 저장한 모습이다. 이런 테이블을 둠으로써 현재 스키마가 어떤 버전까지 적용되었는지 확인할 수 있는 것이다. 

 

Flyway와 H2 데이터베이스를 사용한 로컬 실행

 그러나 문제가 하나 생겼다. 나는 로컬에서 유닛테스트를 실행할 때, 그리고 로컬에서 스프링 백엔드를 실행할 때 H2 데이터베이스를 사용 중이다. 이 경우 새로 올라오는 H2 DB의 스키마가 존재하지 않으니 Flyway는 1.0.0 baseline 버전 DDL부터 실행하려 한다. 만약 서버의 기존 DB 스키마를 쓰려 1.0.0 DDL을 만들어 놓지 않는다면 Flyway는 1.0.0을 건너뛰고 1.0.1을 실행하려 하며, 그 이전 스키마가 H2 에는 없기 때문에 실패할 것이다. 

 한참을 찾아봤지만 이런 경우 어쩔 수 없이 1.0.0 DDL을, 즉 초기 스키마를 반드시 만들어 저장해야 한다. Flyway가 DDL을 실행하므로 hibernate가 DDL을 실행하지 않도록 validate나 none을 반드시 설정해야 하며, 그런 경우 hibernate가 Entity를 기반으로 DDL을 생성하지 않을 뿐더러, Entity는 항상 최신 버전의 DDL을 따르므로 Flyway DDL과 충돌할 것이기 때문이다. 

 로컬에서는 Flyway를 쓰지 않도록 해보려고도 했으나 로컬에서 DDL 또한 모두 테스트 할 수 있으니 그렇게까지 할 필요는 없는 듯 했다. 

 결국 개발 서버 DB에 붙어서 workbench로 DDL들을 추출해 1.0.0의 DDL을 만들 수 밖에 없었다. 이 경우 1.0.0은 baseline으로 설정되어 있으므로 그대로 올려도 스키마가 존재하는 개발 서버와 운영 서버에서는 Flyway가 무시한다. 오직 아무 스키마도 없는 로컬 H2 데이터베이스에서만 실행된다. 

 

마무리

 이번에는 Flyway를 사용해 초기 서비스의 스키마 변경을 안정적으로 다루어 보았다. hibernate의 DDL 자동 생성보다 직접 스키마를 관리한다는 말을 많이 들었지만 매 변경마다 이를 적용할 엄두가 안나서 미루고 있던 것을 이렇게 쉽게 관리할 수 있어서 정말 신기했다. 아마 다른 프로젝트를 또 하게된다면 그땐 처음부터 Flyway를 써서 스키마를 관리하지 않을까 싶다.