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

74 lines
7.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: error / flyway-launcher-divergent-migration-set-shared-dev-db-2026-06-12
source_type: error-note
status: raw
related_branches: [feature-domain-event-outbox-contract, feature-persistence-auditing-contract]
related_projects: [ca-skeleton]
tags: [error, ca-skeleton, flyway, migration, classpath, gradle, ide, testcontainers]
created: 2026-06-12
status_label: resolved
---
# error: flyway-launcher-divergent-migration-set-shared-dev-db-2026-06-12
> Layer: `raw/errors/` — 작업 중 마주친 단일 실패·트러블슈팅 기록. 원본은 raw에 영구 보관한다.
## Parent / 부모
- [[raw/branch-notes/feature-domain-event-outbox-contract]] — V3__outbox_event.sql 추가가 잠복해 있던 V2 위치 결함을 발화시킴. V2 자체는 feature-persistence-auditing-contract 산출물.
## 증상 / Symptom
- 에러 메시지 (사용자 IDE 실행, 원문 그대로):
```text
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), `OutboxContainerTestSupport` 는 `classpath: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.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 산출물 그대로).
- 검증 방법: 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 추출 후보).
## Related / 관련
- 관련 에러: [[raw/errors/method-security-class-pointcut-final-usecase-bean-2026-06-12]] — 같은 날 같은 branch 의 직전 기동 실패 (bean 등록↔클래스 레벨 pointcut). 두 건 모두 "모듈 경계(테스트 전용 의존/샘플 fixture)가 런타임 배선과 만나는 지점"에서 터진 결함.