개발 이야기/Spring

[Spring] Flyway 가이드 (Kotlin + Spring + R2DBC)

제이온 (Jayon) 2022. 11. 3.

Flway 이론

간단 개념

Flyway는 DB 마이그레이션 도구로서 데이터베이스 형상관리를 위해서 사용합니다.

 

형상 관리란 소프트웨어의 변경 사항을 체계적으로 추적하고 통제하는 것으로, 형상 관리는 일반적인 단순 버전 관리 기반의 소프트웨어 운용을 좀 더 포괄적인 학술 분야의 형태로 넓히는 근간을 이야기합니다. 우리에게 친숙한 Git을 떠올리시면 됩니다.

 

 

동작 방식

Flyway가 연결된 데이터베이스에 자동으로 SCHEMA_VERSION이라는 메타 데이터 테이블을 생성합니다. Flyway는 사용자가 정의한 sql의 파일명을 자동으로 스캔하여, SCHEMA_VERSION에 버전 정보를 남기는 동시에, 데이터베이스 측에 변경 내용을 적용합니다. SCHEMA_VERSION 테이블에는 다음 정보를 담고 있습니다.

 

 

중요한 컬럼만 부가 설명하겠습니다. 체크섬 개념이 가장 중요합니다!!!

 

  • version: 말 그대로 버전을 의미합니다.
  • script : 어떤 스크립트를 돌렸는지 스크립트 파일명을 알 수 있습니다.
  • checksum: 스크립트 파일 내용의 해시 값으로 파일 내용이 변경되면 에러를 일으킬 수 있습니다.

 

명령어

  • migrate: sql 파일들을 읽어서 변경된 스키마를 DB에 마이그레이션합니다.
  • clean: flyway로 생성한 스키마 외의 해당 데이터 베이스의 모든 테이블을 삭제합니다. (절대 서비스에 사용하면 안됨 – 테스트 서버에서도 사용 자제)
  • info: DB에 적용된 마이그레이션 정보와 로컬에 남아 있는 (sql 파일의) 상태의 마이그레이션 정보를 보여줍니다.
  • validate: DB에 적용된 마이그레이션 정보의 유효성을 검증합니다.
  • undo: 이전에 실행한 마이그레이션을 취소합니다. (유료 버전만 존재)
  • repair: 마이그레이션이 실패한 내역을 수정합니다.
    • 실패한 마이그레이션 항목 제거 (DDL 트랜잭션을 지원하지 않는 데이터베이스에만 해당 - ex. MySQL)
    • 이미 적용된 마이그레이션의 체크섬을 사용 가능한 마이그레이션의 체크섬으로 재정렬.
      • PostgreSQL은 DDL 트랜잭션을 지원하므로 repair를 사용할 때 해당 케이스로 동작합니다.
      • (부연 설명) 이미 마이그레이션이 완료된 sql 파일(체크섬: A)이 있고 모종의 이유로 수정하고 싶다고 가정합시다. 해당 sql 파일의 내용을 로컬에서 변경할 경우 체크섬이 B로 바뀌는데, 이를 그대로 마이그레이션을 수행하면 에러가 발생합니다. 이때 repair를 사용하면 에러 없이 자동으로 파일의 내용을 토대로 체크섬을 A에서 B로 맞춰주고, 변경된 sql 내용도 새로 수행합니다. 이때 기존 체크섬 A였던 파일에서 수행한 sql 내용이 롤백되지 않는 점을 주의하셔야 합니다.
      • 하지만 git reset -—hard와 비스무리한 느낌이다보니 이미 성공한 sql 파일의 내용을 수정하는 것은 권장하지 않습니다.
      • https://stackoverflow.com/questions/25775545/flyway-when-is-repair-required-for-a-postgresql-database
  • baseline: flyway로 형상 버전 관리를 시작할 baseline을 설정합니다.

 

장단점

  • 장점
    • DDL 자동화.
    • SQL 이력 관리 가능.
  • 단점
    • 무료 버전은 롤백 기능이 없음.
    • DBA 권한을 엔지니어에게 일부 주어야 하므로, 엔지니어의 실수로 인해 잘못된 SQL로 데이터 유실이 발생할 수 있음.

 

Flyway 사용 방법

SQL 파일 생성

마이그레이션을 수행할 SQL 파일을 생성해야 합니다. 기본 경로는 resources/db/migration이며, 해당 디렉토리 안에 SQL 파일을 일정 규칙에 따라 만들면 됩니다.

 

파일명 컨벤션

 

  • Separator은 언더바(_)가 2개임을 주의해야합니다.

 

prefix

  • V : versioned migration
  • U : undo migration (유료 버전만 적용가능)
  • R : Repeatable migration (파일 하나로 수정해서 작업 가능)

 

Version

  • 버전은 유일해야 합니다.
  • 버전은 반드시 기존의 버전보다 값이 커야 합니다.
    • 가령 V1 다음에는 V2가 와야 함.
  • 위 내용을 종합해 봤을 때, 버전명은 주로 날짜 형식을 쓰는 편입니다.

 

마이그레이션 파일을 쉽게 작성하도록 도와주는 플러그인

 

 

 

 

스키마가 비어 있는 경우

  • 초기 스키마 sql 파일을 작성합니다. (주로 init이라는 명칭 사용)
  • 스키마 변경 시 마다 변경 내용 sql 파일을 작성합니다.
  • 마이그레이션이 성공한 기존 sql 파일은 절대로 수정하면 안됩니다.

 

이미 스키마가 존재하는 경우

  • sql 내용이 비어 있는 빈 스키마 파일을 생성합니다. (초기 스키마 파일은 무조건 있어야 함.)
  • 해당 빈 스키마 파일의 버전에 대해 baseline 설정을 하여 기본 시작점을 명시해야 합니다.
    • baseline 설정을 하게 되면 base가 되는 버전을 포함하여, 이전 버전들은 마이그레이션하지 않고 이후의 버전들만 마이그레이션하게 됩니다.
    ## application.properties / application.yml
    
    spring.flyway.baseline-on-migrate=true
    spring.flyway.baseline-version= [기준의 되는 버전]
    
  • 스키마가 변경 될때마다 새로운 버전의 sql 파일 생성합니다.

 

주의 사항

잘못된 SQL 입력으로 실행 fail 하는 경우

사전 지식

마이그레이션 파일의 SQL 쿼리들은 모두 하나의 트랜잭션 안에 감싸지게 됩니다. 그래서 일반적으로 한 마이그레이션 파일 내부의 SQL 쿼리 중 일부가 실패한다면 모든 쿼리가 실패하며, 마이그레이션 작업도 멈추게 됩니다.

 

이때 PostgreSQL과 같이 DDL 트랜잭션을 지원하는 DB는 DDL이든 DML이든 같이 하나의 트랜잭션 안에 묶일 수 있어서, 어떠한 SQL 쿼리가 실패한다면 전체 쿼리가 롤백됩니다. 또한, flyway_schema_history에도 실패한 마이그레이션 이력이 남지 않습니다.

 

하지만 그 외 DDL 트랜잭션을 지원하지 않는 DB는 DDL과 DML을 동시에 사용하거나 DDL만 이루어진 쿼리는 하나의 트랜잭션 안에 묶일 수가 없으므로, 트랜잭션 자체가 사용되지 않아서 롤백 기능이 동작하지 않습니다. (트랜잭션 안에 비트랜잭션 구문이 들어갈 수 없기 때문입니다.) 가령 insert 쿼리와 create table 쿼리를 혼합하였는데, insert 쿼리가 성공하고 create table 쿼리에서 오류가 난다면 트랜잭션 롤백이 발생하지 않습니다.

 

이 때문에 flyway에서는 DDL 트랜잭션을 지원하는 DB에 대해서는 마이그레이션이 실패했을 때, flyway_schema_history에 실패한 마이그레이션 이력을 기록합니다.

 

https://documentation.red-gate.com/rcc/concepts/understanding-migrations-and-callbacks/transactions

 

DDL 트랜잭션을 지원하는 DB (ex. PostgreSQL)

실패한 마이그레이션 파일의 내용을 수정하여 다시 migrate를 수행하면 됩니다.

 

그 외 DB (ex. MySQL)

repair()를 수행하여 실패한 마이그레이션 이력을 지우고 (혹은 직접 형상 관리 테이블의 실패한 마이그레이션 이력 레코드를 지우고), 실패한 마이그레이션 파일의 내용을 수정하여 다시 migrate를 수행해야 합니다.

 

SQL 파일 관련

  • 가능한 하나의 파일에는 하나의 SQL 문을 담도록 합시다.
  • 버전은 겹치지 않고, 이전 버전보다 높게 설정해 주세요.
  • 이미 마이그레이션 성공한 파일의 내용은 절대 수정하지 마세요.

 

롤백 관련

undo 명령어를 무료 버전에서 지원하지 않으므로 롤백하기 위해 별도의 SQL 파일을 만들어서 migrate하면 됩니다. 가령, create table을 실수로 했다면 drop table의 내용이 담긴 SQL 파일을 작성해서 새로 migrate하는 것이죠.

 

Spring 프로젝트에 Flyway 적용

기술 스택: Kotlin + Spring Boot + R2DBC + Gradle

 

의존성

implementation("org.flywaydb:flyway-core")
implementation("org.postgresql:postgresql")

현 프로젝트는 r2dbc postgresql driver를 사용하고 있는데, flyway는 jdbc driver만 허용하므로 이를 연결할 수 있는 별도의 jdbc postgresql driver가 필요해서 2번째 라인을 추가했습니다.

 

application.yml

spring:
  ...
	flyway:
	    enabled: true
	    baseline-on-migrate: true
	    baseline-version: 20220826133347
	    url: jdbc:postgresql://${CONF_TRANSACTION_GATEWAY_DB_HOST:localhost}:${CONF_TRANSACTION_GATEWAY_DB_PORT:5432}/${CONF_TRANSACTION_GATEWAY_DB_DATABASE:port}
	    user: ${CONF_TRANSACTION_GATEWAY_DB_USERNAME:postgres}
	    password: ${CONF_TRANSACTION_GATEWAY_DB_PASSWORD:postgres}
  • enabled: flyway를 사용하겠다는 의미입니다.
  • baseline-on-migrate: flyway_schame_history 테이블을 자동으로 생성해 준다는 의미입니다.
  • baseline-version: baseline에 해당하는 버전을 의미합니다. (이미 스키마가 있는 DB에 한해서 설정하면 됨.
  • url, user, password: flyway를 적용할 DB의 연결 정보를 작성합니다. (이때 url은 반드시 jdbc driver를 사용해야 함.)

 

FlywayConfig

@Configuration
class FlywayConfig() {

    @Bean
    fun flyway(flywayProperties: FlywayProperties): Flyway {
        val flyway = Flyway.configure()
            .baselineOnMigrate(flywayProperties.isBaselineOnMigrate)
            .baselineVersion(flywayProperties.baselineVersion)
            .dataSource(flywayProperties.url, flywayProperties.user, flywayProperties.password)
            .load()

        // PostgreSQL은 자동으로 실패한 DDL을 롤백해줍니다.
        // 그래서 repair()을 수행하면 이미 적용된 마이그레이션의 체크섬을 사용 가능한 마이그레이션의 체크섬으로 재정렬합니다.
        flyway.repair()
        flyway.migrate()
        return flyway
    }

    @Bean
    fun flywayProperties(): FlywayProperties {
        return FlywayProperties()
    }
}

 

  • gradle에 implementation("org.springframework.boot:spring-boot-starter-jdbc") 혹은 implementation("org.springframework.boot:spring-boot-starter-data-jpa")과 같은 jdbc 기반 spring-boot-starter 라이브러리가 들어갈 경우, 스프링 부트 애플리케이션이 띄워질 때 자동으로 DB와 연결해서 마이그레이션을 진행합니다.
  • 현 프로젝트에도 위 라이브러리를 추가해서 진행할 수는 있지만, 불필요한 라이브러리라 판단되어 위와 같이 설정 클래스를 만들었습니다. 사실 위 라이브러리를 사용한다고 하더라도, flyway를 명확하게 사용하고 있음을 팀원에게 보여줄 수 있고, 유연하게 세팅을 변경할 수 있으므로 설정 클래스 방식도 좋다고 생각합니다.
  • 별도로 Flyway 객체를 빈으로 등록하고, 그 과정에서 곧바로 migrate() 메소드를 수행하도록 설정하면, 스프링 부트 애플리케이션이 구동될 때 Flyway 빈을 등록하기 직전 마이그레이션을 진행합니다.
  • 참고로 FlywayProperties 객체는 application.yml에서 Flyway 관련된 설정만 뽑아서 프로퍼티에 저장한 객체입니다.

댓글

추천 글