Files
llm-wiki/raw/errors/flyway-launcher-divergent-migration-set-shared-dev-db-2026-06-12.md
T

7.5 KiB
Raw Blame History

title, source_type, status, related_branches, related_projects, tags, created, status_label
title source_type status related_branches related_projects tags created status_label
error / flyway-launcher-divergent-migration-set-shared-dev-db-2026-06-12 error-note raw
feature-domain-event-outbox-contract
feature-persistence-auditing-contract
ca-skeleton
error
ca-skeleton
flyway
migration
classpath
gradle
ide
testcontainers
2026-06-12 resolved

error: flyway-launcher-divergent-migration-set-shared-dev-db-2026-06-12

Layer: raw/errors/ — 작업 중 마주친 단일 실패·트러블슈팅 기록. 원본은 raw에 영구 보관한다.

Parent / 부모

증상 / Symptom

  • 에러 메시지 (사용자 IDE 실행, 원문 그대로):
    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 으로만 의존.

  1. Gradle bootRun (runtime classpath — V2 미포함) → Flyway 가 {V1,V3} 해석·적용. history = {1,3}.
  2. IDE Run (VSCode/JDT — test 의존성이 클래스패스에 합류해 V2 가 보임) → 해석 {V1,V2,V3}, history {1,3} → V2 가 max(3) 아래 미적용 → resolved-not-applied 검증 실패 (out-of-order=false 는 FLYWAY-C5 로 고정).
  3. 반대 방향도 확인: outOfOrder=true 로 V2 를 보정 적용해 history={1,2,3} 을 만들면, 이번엔 Gradle 실행(해석 {V1,V3})이 applied-not-resolved 검증 실패. 즉 어느 쪽으로 "고쳐도" 다른 launcher 가 깨짐.
  4. 위 13 은 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-시나리오 실측 → 양방향 검증 실패 확정.
  • 2026-06-12 — V2 소비자 전수 조사: 샘플 테스트는 실 DB/Flyway 미사용(mock), OutboxContainerTestSupportclasspath:db/migration 마이그레이션이지만 outbox/idempotency 테이블만 사용, application.yml locations 미지정(기본값), compose init 마운트 없음 → V2 이동의 파급 없음 확인.

근본 원인 / Root cause

  • 직접 원인: 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.sqlsample-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 산출물 그대로).
  • 검증 방법: bootRun 기동 3.324s + healthcheck 200 + ERROR 0건; :sample-portfolio:test 129/129, :app-bootstrap:test 224/224 (Testcontainers outbox 계약 5종 — V1+V3 만 적용으로도 green, ArchUnit 48 rules).
  • 잔여 위험: 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 추출 후보).