Post

flyway 검증을 런타임이 아닌 빌드 시점에 수행하도록 하는 방법

flyway 검증을 런타임이 아닌 빌드 시점에 수행하도록 하는 방법

image.png

📌 개요

flyway 는 데이터베이스조차 버전 관리하게 해 주는 놀라운 오픈소스 도구이다. 나는 이 혁명적인 도구에 감탄을 금치 못하며 게시글을 작성해버렸다. 장점이 정말 명확한 flyway는 그만 단점까지 명확해져 버렸다.

먼저 롤백이 안 된다. 실행 도중 오류가 발생하면 이미 실행한 DDL문은 그대로 반영된다. 이를 복구하기 위해 개발자가 수동으로 처리해야 한다. 유료 버전은 Undo 마이그레이션을 지원하지만, 이 조차 별도로 스크립트를 작성해야 한다는 점은 개발자로서 매우 불편하고 짜증나고 보기 안 좋다.

이전 내용의 연장선이긴 한데, 이미 적용된 스크립트는 수정이 불가능하다. 이미 적용된 파일이 변경되면 flyway_schema_history 의 체크섬 값이 달라져 오류가 발생한다. 그럼 만약에 이미 적용된 마이그레이션 파일에서 오타를 발견했다면? 이를 수정하는 새로운 스크립트를 작성해야 한다. 매우 보기 안 좋고 짜증나고 불편하다.

이건 실무적인 내용인데, 생각보다 마이그레이션 파일의 이름 오타와 버전 충돌로 인해 서버가 죽는 문제가 잦다. 왜 파일 버전과 설명 사이의 언더바는 2개여야 하는지 참 원망스럽다. 물론 왜 2개인지는 경험적으로 알고 있다. 사실 뭐 이름 오타가 그렇다 치는데, 버전 충돌은 어쩔 수 없다. merge 시 merge 대상 브랜치의 최신 마이그레이션 파일의 버전이 몇 인지 확인하는 과정은 매우 귀찮고 까먹기 쉽고 짜증나고 불편하다.

📌 POV: 나 혼자만 개발

혼자서 개발을 진행할 때 flyway 검증 관련 문제가 발생하면 하하호호 웃으며 고치면 된다. 파일 버전 충돌 등은 알아서 런타임에 잘 터져주니까 그 때 처리하면 된다.

image.png

실제로 버전 충돌 나면 친절하게 에러 로그도 출력해주고 뻥 터진다. 그럼 고치면 된다.

다만 JPA를 사용하는 경우 조심해야 하는 점이 있는데, JPA Entity 구조와 마이그레이션 파일로 생성된 테이블의 스키마가 일치하지 않아도 정상적으로 실행된다는 것이다.

image.png

잘 보면 마이그레이션 파일의 content 컬럼에는 NOT NULL 이 설정되어야 한다. 그러나 이러한 불일치에도 불구하고 애플리케이션은 문제없이 잘 실행된다.

image.png

실제 데이터베이스에도 마이그레이션 파일의 명세 그대로 적용된 것을 볼 수 있다.

참고로 flyway를 사용한다면 ddl-auto: create 를 사용하면 안 된다. 마이그레이션 파일이 적용되지 않는다. JPA 엔티티 구조로 적용된다.

이러한 Entity 구조와 데이터베이스 스키마의 불일치를 막기 위해서 ddl-auto: validate 로 설정해야 한다.

image.png

참고로 ddl-auto: validate 는 이전 예제의 nullable 불일치같이 사소한(?) 오류는 감지하지 못하므로, 엔티티의 컬럼 이름(content)을 다른 값으로 변경해보는 것으로 validate 가 어떻게 동작하는지 확인해보았다. 오류 로그를 확인해보면 컬럼 이름이 불일치하기에 런타임에 뻥 터진 것을 확인할 수 있다.

정리하면, 혼자 개발한다면 버전 이름 잘 쓰고, 파일명 오타 내지 말고, ddl-auto: validate 로 설정하면 대부분의 문제는 잘 감지하고 해결할 수 있다.

📌 POV: 팀 프로젝트

그 전에 우리 팀의 문제 상황, 왜 flyway의 검증 시점에 대해 고민하게 되었는지 이야기해보겠다. 우리 팀의 CI 스크립트는 빌드가 잘 되는지 확인하고, 테스트 코드가 잘 통과되는지 확인하면 성공적으로 통과하게 된다. 어떻게 보면 일반적인 CI 과정이다. 이제 flyway의 검증이 언제 수행되었는지 생각해보자. 빌드 후 런타임에 수행된다. 그리고 우리는 flyway 검증 관련 테스트 코드를 작성하지 않았기 때문에(그리고 작성할 수 있는지도 모르겠다.) flyway 관련 문제가 발생해도 CI 스크립트는 정상적으로 통과하게 된다.

그렇기에 우리는 flyway 관련 문제를 조기에 발견하지 못하고 서버에 배포가 되는 문제가 발생했었다. 개발자로서 조금 쪽팔린 건, 코드 리뷰 시에도 마이그레이션 파일명 오타를 잡지 못했고, 물론 CI 스크립트도 마찬가지로 오류를 잡지 못했다. 그리고 서버가 죽어도 한 동안 이 사실을 알아채지 못했다. 심지어 서버가 터진 사실을 프론트 개발자로부터 듣게 되었다.

아마 내 기억상엔 이게 처음이 아니었던 것으로 기억한다. 음, 가만 둘 수는 없었다. 문제의 원인은 나름 분명했다. 런타임에 flyway 검증이 이루어지기에, 그리고 이에 관련하여 테스트 코드는 없었기에 CI 스크립트가 잡지 못한 것이다. 그래서 나는 빌드 시점에 flyway 검증을 수행할 수 있도록 하는 방법을 찾기 시작했다.

그 결과 내가 찾은 방법은 gradle 의 플러그인을 사용하는 방법이었다. gradle의 flyway 플러그인을 사용하면 데이터베이스 마이그레이션을 빌드 단계에 통합할 수 있다. 다양한 기능들을 제공하지만, 나는 마이그레이션 파일 이름 검증만 사용하였다.

서론이 길었는데, 이제 진짜로 빌드 단계에서 flyway 검증을 수행하는 방법을 알아보자. build.gradle 만 수정하면 된다.

1
2
3
4
5
6
plugins {
    id 'java'
    id 'org.springframework.boot' version '3.1.5'
    id 'io.spring.dependency-management' version '1.1.4'
    id 'org.flywaydb.flyway' version '9.16.3'
}

먼저 flyway 관련 플러그인을 추가한다. 버전은 사용하고 있는 flyway 의존성 버전에 맞추는 것을 권장한다.

1
2
3
4
5
6
7
dependencies {
    
    // ...
    runtimeOnly 'com.h2database:h2'
    
    // ...
}

H2 데이터베이스 의존성이 없다면 추가한다. 왜 H2 인메모리 데이터베이스를 사용하는지는 후술한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
flyway {
    url = 'jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1'
    user = 'sa'
    password = ''
    locations = ['filesystem:src/main/resources/db/migration']
    validateMigrationNaming = true
    ignoreMigrationPatterns = ['*:pending']
}

tasks.named('build') {
    dependsOn 'flywayValidate'
}

tasks.named('bootJar') {
    dependsOn 'flywayValidate'
}

먼저 아래 두 블록부터 보자. buildbootJar 태스크 전 flywayValidate 를 수행하도록 한다. flywayValidate 태스크가 실행되면 내부적으로 존재하는 데이터베이스를 찾는다. 이 때문에 가상의 인메모리 H2 데이터베이스를 사용하는 것이다. CI 단계에는 운용 중인 데이터베이스가 없기에, flyway를 검증할 때 사용할 임시 데이터베이스가 필요하고, 필요한 요구사항에 가장 적합한 인메모리 데이터베이스를 사용하는 것이다.

flyway 블록을 보자. DB_CLOSE_DELAY=-1 은 H2 데이터베이스에 대한 연결이 끊어져도 JVM이 살아있다면 데이터베이스를 유지하는 옵션이다. 즉, JVM이 종료되면 데이터베이스 또한 사라진다. validateMigrationNamingtrue 로 설정하여 마이그레이션 파일들 이름이 올바른 규칙으로 작성되었는지 검증한다. ignoreMigrationPatterns = ['*:pending'] 은 아직 pending 상태인 마이그레이션을 오류로 감지하지 말라는 설정이다. 우리는 무결성을 확인하는 것이 목적이기 때문이다.

image.png

실제로 파일명 오타를 발생시키고 CI 스크립트를 실행시키면 관련 오류가 발생한다. 🤯

📌 마치며

flyway가 생각보다 까다롭긴 하지만, 충분히 매력적인 도구라고 생각한다. 기술이나 도구를 어떻게 사용하는지에 따라 달라지는 퍼포먼스를 보니, 아직 갈 길은 멀은 것 같다. 세상이 너무 넓다.

This post is licensed under CC BY 4.0 by the author.