Post

Flyway 스키마 검증을 런타임이 아니라 빌드 시점에 동작하도록 하여 서버가 다운되지 않도록 하는 방법

Flyway 스키마 검증을 런타임이 아니라 빌드 시점에 동작하도록 하여 서버가 다운되지 않도록 하는 방법

Flyway 스키마 검증을 런타임이 아니라 빌드 시점에 동작하도록 하여 서버가 다운되지 않도록 하는 방법

Pasted image 20260407142005.png

Flyway란?

Flyway는 데이터베이스 스키마에 대한 형상 관리 도구이다. SQL 마이그레이션 스크립트를 버전별로 관리하여 개발 환경 간 스키마 일관성을 보장한다.

JPA 엔티티와 DB 스키마 불일치

보통 로컬 개발 환경에서는 Flyway를 비활성화한다. 엔티티를 새로 만들거나 수정할 때마다 Flyway 스크립트도 함께 작성해야 하는데, 아직 엔티티 구현이 확정되지 않은 상태에서 이를 강제하면 개발 생산성이 저하되기 때문이다.

그래서 일반적으로 로컬 개발 환경에서는 아래 두 설정을 적용한다.

1
2
3
4
5
6
spring:
  jpa:
    hibernate:
      ddl-auto: create # JPA가 엔티티를 기반으로 스키마를 자동으로 생성하도록
  flyway:
    enabled: false # Flyway 비활성화

만약 JPA 엔티티와 Flyway 스크립트의 스키마가 서로 다르다면 어떻게 될까? ddl-auto 설정에 따라 결과가 달라진다.

create 나 create-drop 로 설정하면 JPA가 스키마를 덮어쓰므로 불일치가 겉으로 드러나지 않는다.

update 로 설정하면 JPA가 DB에 누락된 컬럼을 추가하려고 시도하지만, 타입 불일치나 제약 조건 차이는 그냥 넘어갈 수 있다.

validate 로 설정하면 런타임 시 엔티티와 DB 스키마를 비교한다. 불일치가 감지되면 SchemaManagementException을 던지며 애플리케이션 구동이 중단된다. 따라서 일반적으로 스테이징과 운영 환경에서 이 설정을 사용한다.

validate가 가장 안전한 선택이지만, 문제가 있다. 스키마 불일치를 애플리케이션이 실제로 기동되는 순간에야 알 수 있다는 점이다. 즉, 배포가 완료된 후에야 문제가 발생함을 알 수 있다.


그렇다면 JPA 엔티티와 Flyway 스크립트의 스키마가 일치하는지 어떻게 효과적으로 검증할 수 있을까? ddl-auto=validate 설정은 런타임에 스키마 불일치를 감지해 주지만, 애플리케이션이 실제로 기동될 때까지는 문제가 있는지 알 수 없다는 한계가 있다.

따라서 스키마 불일치를 CI 단계에서 미리 검출하도록 구현해보자. 애플리케이션이 기동되는 것과 최대한 유사한 환경에서 Flyway 마이그레이션과 JPA 검증을 함께 수행해야 한다.

이를 위해 테스트 코드에서 Testcontainers를 사용하여 실제 MySQL 컨테이너를 띄우고, 그 위에서 Flyway 마이그레이션과 ddl-auto=validate 검증을 동시에 수행하도록 구성할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@SpringBootTest
@TestPropertySource(properties = {
        "spring.flyway.enabled=true",
        "spring.flyway.baseline-on-migrate=true",
        "spring.jpa.hibernate.ddl-auto=validate"
})
class FlywayMigrationTest {

    private static final MySQLContainer<?> CONTAINER = new MySQLContainer<>("mysql:8.0");

    static {
        CONTAINER.start();
    }

    @Test
    void flyway_스크립트와_JPA_스키마가_일치하는지_검증한다() {
        // 컨텍스트가 정상적으로 시작되면
        // Flyway 마이그레이션과 ddl-auto=validate 검증이 모두 통과한 것이다.
    }

    static class FlywayMySQLInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {

        @Override
        public void initialize(ConfigurableApplicationContext applicationContext) {
            TestPropertyValues.of(
                    "spring.datasource.url=" + CONTAINER.getJdbcUrl(),
                    "spring.datasource.username=" + CONTAINER.getUsername(),
                    "spring.datasource.password=" + CONTAINER.getPassword()
            ).applyTo(applicationContext.getEnvironment());
        }
    }
}

@TestPropertySource 를 를 통해 Flyway 검증에 필요한 설정들을 적용한다. Flyway가 마이그레이션 스크립트를 모두 실행한 뒤, Hibernate가 DB 스키마와 JPA 엔티티를 비교하도록 순서를 강제한다.

FlywayMySQLInitializer 는 컨테이너가 할당받은 정보(JDBC URL, username, password 등)를 Spring 환경 변수에 주입한다. 컨텍스트가 초기화되기 이전 시점에 실행되어야 하므로 ApplicationContextInitializer를 구현하는 방식을 사용한다.

이 테스트의 검증은 컨텍스트 로드 자체에서 이루어지므로, 테스트 메서드 본문이 없다. 스키마 불일치가 존재한다면 SchemaManagementException이 발생하며 컨텍스트 로드가 실패하고, 곧바로 테스트도 실패한다.

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