website: Flyway stage 안전장치, env var에서 코드로 옮기기
기존엔 stage에서 체크섬 불일치(마이그레이션 파일 수정 등)가 감지되면 DB를 통째로 날리고 처음부터 재적용하도록 `.env.stage`에 `SPRING_FLYWAY_CLEAN_ON_VALIDATION_ERROR=true`라는 env var를 넣어뒀었다. 그런데 Spring Boot 3.5.16이 물고 오는 Flyway 10.x에서 이 프로퍼티 자...
#133에서 Flyway를 도입하고 나서 dev→stage CD가 한 번 실패했다. 자동 롤백돼서 데이터 영향은 없었지만, 원인을 보니 그냥 넘어갈 문제가 아니었다.
가장 쉬운 우회는 Flyway를 9버전대로 내리는 거였다. 그런데 Spring Boot 3.5.x는 애초에 auto-configuration 자체가 Flyway 10.x에 맞춰져 있어서, 억지로 버전을 내리면 이 프로퍼티는 살아나겠지만 다른 연동 지점이 검증 안 된 조합으로 깨질 위험이 있었다. 버전을 낮추는 대신 같은 동작을 코드로 재구현하는 쪽을 택했다. FlywayConfig.java에 @Profile("stage") 전용 FlywayMigrationStrategy 빈을 하나 추가해서, flyway.migrate()가 FlywayValidateException을 던지면 clean() 후 다시 migrate()를 호출하도록 만들었다.
@Bean
@Profile("stage")
public FlywayMigrationStrategy stageCleanOnValidationErrorStrategy() {
return flyway -> {
try {
flyway.migrate();
} catch (FlywayValidateException e) {
flyway.clean();
flyway.migrate();
}
};
}
이렇게 코드로 옮기고 나니 env var 방식보다 나은 점이 하나 더 보였다. env var는 이름 문자열이라 실수로 .env.prod에 그대로 복붙하면 prod도 자동 clean 대상이 될 수 있었다 — 실제로 그런 실수가 있었던 건 아니지만 구조적으로 가능한 경로였다. @Profile 분리는 이 실수 자체를 원천 차단한다. prod 쪽엔 이 빈이 아예 생성되지 않으니까. 실제 서버 조치는 .env.stage에서 문제의 줄을 지우는 것뿐이었고, .env.prod는 원래 이 변수가 없어서(대신 SPRING_FLYWAY_CLEAN_DISABLED=true만 있고 이건 여전히 유효) 그대로 안전했다.
이 변경을 infra/db-migration.md에 "진행 상황 (2026-07-24)" 섹션으로 정리해두면서, 동시에 db-man 스킬 문서(backend/.claude/skills/db-man/SKILL.md)도 손봤다. 여기엔 별개로 전에 발견했던 SQLiteDialect 버그(identity PK를 생성할 땐 SQLite rowid 규칙대로 integer로 만들면서 검증할 땐 일반 규칙(Long→bigint)으로 비교해서 자기가 만든 테이블조차 자기 검증에 실패하는 문제) 설명이 있었는데, 왜 이런 버그가 나는지에 대한 배경 설명(SQLite에서 자동증가 PK가 실제로 동작하려면 INTEGER PRIMARY KEY로 선언돼야 rowid의 별칭이 된다는 SQLite 자체 규칙)을 추가했다. 그리고 SchemaMigrationConsistencyIntegrationTest가 실제로 뭘 검증하는지(빈 DB에 마이그레이션 SQL을 전부 적용한 뒤 ddl-auto=validate로 실제 기동해보는 것), enum 값 CHECK 제약이 왜 자동 검증에 안 걸리는지도 좀 더 풀어서 적었다. 이런 문서는 나중에 다른 사람이(혹은 몇 달 뒤의 내가) 왜 이렇게 짜여 있는지 다시 추적하지 않아도 되게 만드는 게 목적이라, 결론만 적기보다 이유를 같이 적어두는 편이 낫다고 판단했다.