Error creating bean with name 'flywayInitializer' ... : Flyway forward-only migration failed during startup
스크래치 DB 재현 시 실제 원인 메시지: Validate failed: Migrations have failed validation (Flyway 11.7.2).
발생 컨텍스트: 같은 로컬 dev PostgreSQL(ca-pg, localhost:5432/ca_skeleton)을 Gradle bootRun 과 IDE Run 이 공유. Gradle bootRun 은 정상 기동, IDE Run 만 실패 — 동일 코드, 동일 DB.
재현 가능 여부: always (클래스패스 조합 재현 시).
재현 절차 / Reproduction
전제: V1__idempotency_record.sql(adapter-persistence), V2__work_log.sql(sample-portfolio, 기본 db/migration), V3__outbox_event.sql(adapter-persistence). app-bootstrap 은 sample-portfolio 를 testImplementation 으로만 의존.
Gradle bootRun (runtime classpath — V2 미포함) → Flyway 가 {V1,V3} 해석·적용. history = {1,3}.
IDE Run (VSCode/JDT — test 의존성이 클래스패스에 합류해 V2 가 보임) → 해석 {V1,V2,V3}, history {1,3} → V2 가 max(3) 아래 미적용 → resolved-not-applied 검증 실패 (out-of-order=false 는 FLYWAY-C5 로 고정).
반대 방향도 확인: outOfOrder=true 로 V2 를 보정 적용해 history={1,2,3} 을 만들면, 이번엔 Gradle 실행(해석 {V1,V3})이 applied-not-resolved 검증 실패. 즉 어느 쪽으로 "고쳐도" 다른 launcher 가 깨짐.
위 1–3 은 Flyway 11.7.2 단독 하네스(java single-file + filesystem locations + 스크래치 DB)로 4-시나리오 전부 실측 (STEP1 OK / STEP2 FAIL / STEP3 OK / STEP4 FAIL).
조사 단계 / Investigation log
2026-06-12 — 사용자가 "여전히 Flyway 오류" 보고. ca-pg 는 Up, 5432 리스닝, Gradle bootRun 은 2회 연속 정상 기동 → connection refused 아님, launcher 차이로 압축.
2026-06-12 — flyway_schema_history = {1, 3}, 레포 마이그레이션 = V1/V2/V3. V2 는 sample-portfolio 소속 + app-bootstrap testImplementation 전용 → Gradle 런타임에서 V2 비가시 확인.
2026-06-12 — Gradle cache 의 flyway-core 11.7.2 + flyway-database-postgresql + pg driver + jackson 으로 단독 하네스 구성, 스크래치 DB 에서 4-시나리오 실측 → 양방향 검증 실패 확정.
직접 원인: V3 적용(2026-06-12 Gradle 실행) 시점에 V2 가 런타임 클래스패스에 없어 history 에 V2 구멍이 생김 → V2 가 보이는 launcher 의 검증 실패.
근본 원인: fixture 모듈(sample-portfolio)의 마이그레이션이 production 과 같은 기본 location(db/migration)·같은 버전 네임스페이스를 공유하면서, launcher 별로 클래스패스 합류 여부가 달라짐 — 하나의 long-lived dev DB 에 대해 "해석되는 마이그레이션 집합"이 실행 방법에 따라 달라지는 구조. V2 파일 자체의 주석("production 은 V1 만 돈다")은 V3 등장 전의 가정.
트리거 조건: 기본 location 의 fixture 마이그레이션 + 그보다 큰 버전의 production 마이그레이션 추가 + launcher 간 클래스패스 차이 + 공유 dev DB.
Sources / 근거
로컬 검증: Flyway 11.7.2 단독 하네스 4-시나리오 실측 출력 (STEP1 OK migrationsExecuted=2 / STEP2 FAIL Validate failed / STEP3 OK migrationsExecuted=1 / STEP4 FAIL Validate failed) — locally-verified.
ca-tmpl application.yml L133-135: out-of-order: false 주석 "reject out-of-order migrations — preserve cross-developer ordering consistency (FLYWAY-C5). Enabling under prod is forbidden (D4)." — 보정 적용(outOfOrder) 경로가 계약상 막혀 있음의 근거.
Flyway 의 location 재귀 스캔/검증 규칙에 대한 공식 문서 인용은 미보강 (needs-confirmation — flywaydb.org locations/validate 절 인용 권고).
해결 / Resolution
적용한 조치: V2__work_log.sql 을 sample-portfolio/src/main/resources/db/migration/ → db/sample-migration/ (기본 스캔 위치 밖 sibling) 으로 git mv. 파일 헤더의 낡은 가정 문단을 "왜 이 위치인가 + 활성화 방법(spring.flyway.locations 에 location 추가) + 로컬은 ddl-auto=update 가 sample 스키마 담당" 으로 교체. 결과: 모든 launcher 가 동일하게 {V1,V3} 해석 → 현 dev DB history {1,3} 과 일치 → 양쪽 검증 통과. DB 데이터/이력 무변경 (work_log 테이블은 기존 ddl-auto 산출물 그대로).
잔여 위험: IDE(JDT)가 이전 빌드 산출물(build/resources/main/db/migration/V2__work_log.sql 또는 JDT bin 출력)을 캐시하고 있으면 한 번 더 실패할 수 있음 — Java 프로젝트 reload/clean 필요. fork 한 프로젝트가 sample 을 런타임에 켜려면 location 추가가 필요함을 헤더에 명시.
회고 / Lessons
빨리 감지하는 신호: "Gradle 로는 되는데 IDE 로만 Flyway validate 실패" → launcher 별 클래스패스의 db/migration 자원 차이부터 비교 (find */src/main/resources -path '*db/migration*' + flyway_schema_history 대조).
예방 체크리스트: fixture/optional 모듈의 마이그레이션은 기본 db/migration 에 두지 않는다 (Flyway 는 location 을 클래스패스 루트 전체에서 재귀 스캔). 새 production 마이그레이션 버전을 딸 때 비-런타임 모듈에 더 낮은 미적용 버전이 남아 있는지 확인.
디버깅 기법: Flyway 동작이 기억과 다를 수 있는 검증 규칙(resolved-not-applied vs applied-not-resolved 의 fatal 여부)은 Gradle cache jar 로 1-파일 하네스를 만들어 스크래치 DB 에 실측하는 것이 추측보다 빠르다 (이번 4-시나리오 실측이 해결 방향을 결정).
wiki 일반화 후보: "마이그레이션 집합은 클래스패스의 함수다 — launcher 가 둘이면 마이그레이션 소스도 둘" (wiki/concepts 추출 후보).