Flyway 모놀리스 마이그레이션 함정
증상
데이터 소스 또는 마이그레이션 변경 후 Flyway 부트스트랩 중에 백엔드 시작이 실패하거나 중단됩니다. 2026년 5월 및 6월 수정 사항의 일반적인 패턴은 다음과 같습니다.
- Flyway는 런타임
lumie_app역할과 연결하고 테이블 소유권이 필요한 DDL에 실패합니다. CREATE INDEX CONCURRENTLY를 사용하는 마이그레이션에서 새로운 환경이 중단되거나 교착 상태가 됩니다.- 누군가 새 마이그레이션을 추가하는 대신 이미 적용된
V<number>__*.sql파일을 편집하면 부팅이 중단됩니다.
관련 현재 계약은 백엔드 인프라 및 마이그레이션 가이드 참조 페이지에 있습니다.
가능한 원인
RoutingDataSourceConfig는 전용@FlywayDataSource를 잃어버렸기 때문에 Flyway는 마이그레이션 자격 증명 대신 런타임 풀로 대체되었습니다.spring.flyway.user/password또는spring.flyway.postgresql.transactional-lock=false가application.yaml에서 사라졌습니다.- 이미 실행된 마이그레이션이 제자리에서 수정되었습니다.
2ff2bb48커밋은 표준적인 예입니다. 실수로 편집한 내용을V18__rls_baseline.sql로 되돌린 것입니다.
진단
먼저 두 개의 현재 기준 파일을 확인하세요.
RoutingDataSourceConfig는 여전히 별도의 마이그레이션 데이터 소스를 생성해야 합니다.
// lumie-backend/app/src/main/java/com/lumie/app/config/RoutingDataSourceConfig.java
@Bean
@FlywayDataSource
public DataSource flywayMigratorDataSource(
@Qualifier("primaryDataSourceProperties") DataSourceProperties primary,
@Value("${spring.flyway.user}") String migratorUser,
@Value("${spring.flyway.password}") String migratorPassword) {
return DataSourceBuilder.create()
.url(primary.determineUrl())
.driverClassName(primary.determineDriverClassName())
.username(migratorUser)
.password(migratorPassword)
.build();
}
lumie-backend/app/src/main/resources/application.yaml는 여전히 Flyway 자격 증명 및 비트랜잭션 권고 잠금 모드를 고정해야 합니다.
spring:
flyway:
user: ${DB_MIGRATOR_USERNAME:${DB_USERNAME:lumie}}
password: ${DB_MIGRATOR_PASSWORD:${DB_PASSWORD:lumie}}
postgresql:
transactional-lock: false
그런 다음 마이그레이션 차이점 자체를 검사합니다.
cd lumie-backend
git diff -- app/src/main/resources/db/migration/public
예상되는 성공 신호: 새 마이그레이션 파일만 나타납니다. 이전 버전의 파일이 변경된 경우 이를 주요 용의자로 간주합니다.
수정
- Flyway가 런타임 풀을 사용하는 경우
app/src/main/java/com/lumie/app/config/RoutingDataSourceConfig.java에서 전용 Flyway 데이터 소스를 복원합니다. - 마이그레이션자 역할이 실수로 구성에서 삭제된 경우
spring.flyway.user/password를 복원합니다. - 처음부터 환경이
V21및V23와 같은 이전 동시 인덱스 마이그레이션으로 인해 교착 상태에 빠지지 않도록spring.flyway.postgresql.transactional-lock=false를 유지하세요. - 이미 적용된 버전 마이그레이션의 편집 내용을 되돌리고 대신 새로운 전달 전용 마이그레이션을 제공합니다. 체크섬 복구를 일반적인 경로로 의존하지 마십시오.
특히 SMS 출시의 최종 형태는 다음과 같습니다.
V76__sms_provider_dispatch_tracking.sql는 공급자 열과 인덱스를 소유합니다.V77__sms_provider_dispatch_tracking_indexes.sql는 의도적으로 트랜잭션 무작동(no-op)을 적용하므로 이를 직면하는 환경이 안전하게 진행될 수 있습니다.
예방
- Flyway와 런타임 풀을 다른 보안 경계로 처리합니다. 런타임 역할은 RLS 바인딩 및 비소유 상태를 유지해야 합니다. 마이그레이션자 역할은 DDL을 소유합니다.
- PostgreSQL
CONCURRENTLY가 필요한 인덱스를 추가할 때는 기본 Flyway 트랜잭션 모델로 해결된다고 가정하지 말고 마이그레이션과 잠금 전략을 함께 설계합니다. - 이미 어느 환경에든 적용된
V<number>__*.sql파일은 편집하지 않습니다. Lumie 마이그레이션 가이드도 이를 강한 규칙으로 취급합니다. - datasource wiring을 변경할 때는
RoutingDataSourceConfig.java의 bean wiring과application.yaml의 role/lock 설정을 함께 확인합니다.
운영 세부사항
이 실패군은 평범한 SQL 문법 오류나 Spring 부팅 문제처럼 보일 수 있지만, 실제 회귀는 ownership 경계가 깨진 경우가 많아 위험합니다. 애플리케이션 role과 migration role은 서로 바꿔 쓸 수 없습니다.
| 역할 | 기대 동작 | 혼동될 때의 실패 |
|---|---|---|
lumie_app | RLS가 적용된 애플리케이션 경로로 런타임 읽기/쓰기를 수행합니다. | DDL이 실패하거나 RLS ownership을 약화시키는 임시방편이 생깁니다. |
| Flyway migrator | bootstrap 중 forward-only schema 변경을 소유합니다. | 로컬에서는 통과해도 fresh 또는 production-like 환경에서 실패할 수 있습니다. |
진단할 때는 세 질문을 분리합니다.
- Flyway가 migrator credential로 연결하고 있는가?
- migration 자체가 forward-only이고 이미 이전 버전을 실행한 환경과 호환되는가?
- PostgreSQL lock mode가 과거 migration chain의
CONCURRENTLY문과 호환되는가?
세 번째 문제를 오래된 migration rewrite로 해결하지 마십시오. checksum이 이미 기록된 모든 환경으로 실패를 옮길 뿐입니다.
복구 절차
- 여러 pod가 같은 Flyway 오류로 crash-loop 중이면 rollout을 멈춥니다. 실패 pod 하나는 로그 보존용으로 남깁니다.
- 마지막 Spring boot 실패가 아니라 첫 번째 Flyway 예외를 읽습니다. 보통 migration version, role, lock, checksum 문제가 첫 예외에 드러납니다.
- 영향받은 DB의
flyway_schema_history를app/src/main/resources/db/migration/public파일과 비교합니다. - 적용된 파일이 바뀌었다면 해당 파일을 원래대로 복원하고 새
V<number>__*.sqlmigration으로 forward fix를 만듭니다. - ownership 또는 permission 오류라면 SQL을 바꾸기 전에
@FlywayDataSource와spring.flyway.*credential을 복원합니다. - concurrent-index lock 실패라면
transactional-lock=false를 유지하고 fresh DB에서 migration 전체 chain을 검증합니다.
검증 증거
사고를 닫기 전에 다음 증거를 모읍니다.
- fresh local 또는 staging DB에서 Flyway enabled 상태로 backend가 시작됩니다.
flyway_schema_history에서 새 migration이success로 표시됩니다.- 최종 diff에 이미 적용된 migration 파일 변경이 없습니다.
- application traffic은 migrator datasource가 아니라 runtime datasource를 사용합니다.
- tenant-scoped query에 대한 RLS 확인이 여전히 통과합니다.
피해야 할 패턴
- startup을 빨리 통과시키기 위해 application role로 Flyway를 실행하는 것.
- historical migration을 편집하고
flyway repair를 일반 복구 경로로 사용하는 것. - 빈 DB에서 migration chain 전체를 재생하지 않고
transactional-lock=false를 제거하 는 것. - local H2 또는 일부만 migration된 개발 DB를 production readiness 증거로 삼는 것.
- PostgreSQL
CONCURRENTLY가 필요한 인덱스를 추가할 때 기본 Flyway 트랜잭션 모델이 작동할 것이라고 가정하는 대신 의도적으로 마이그레이션 및 잠금 전략을 설계하세요. - 기존
V<number>__*.sql를 어디에든 적용한 후에는 절대 편집하지 마세요. Lumie의 자체 마이그레이션 가이드에서는 이미 이를 엄격한 규칙으로 취급합니다. - 데이터 소스 배선이 변경되면 두 소스 파일(빈 배선용
RoutingDataSourceConfig.java및 역할 및 잠금 설정용application.yaml)을 함께 확인합니다.