From 6146e151a005a2be2977663daed329fbc779d66e Mon Sep 17 00:00:00 2001 From: thxforall <113906780+thxforall@users.noreply.github.com> Date: Thu, 30 Apr 2026 16:03:23 +0900 Subject: [PATCH] =?UTF-8?q?docs(plans):=20SeaORM=20=E2=86=94=20Supabase=20?= =?UTF-8?q?=EB=A7=88=EC=9D=B4=EA=B7=B8=EB=A0=88=EC=9D=B4=EC=85=98=20?= =?UTF-8?q?=ED=86=B5=ED=95=A9=20epic=20=EB=B6=84=ED=95=B4=EC=95=88=20(#374?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #374 epic 의 design spec — 본 PR 은 spec 작성만, 실행 X. 채택된 방향 + sub-issue 4개 후보를 정의하며, 실행은 #373 (PR #378) 의 1주 0-error 검증 후 시작. 분석 결과: - 현재 SeaORM 62개 vs Supabase CLI 6개 (RawSql 미사용 → SeaORM-native DDL) - dev 는 SKIP_DB_MIGRATIONS=1 로 이미 Supabase 단일, prod 만 두 시스템 공존 - → Option B (SeaORM 폐기) 채택 Phase 분해: 1. baseline — prod schema dump → 1개 idempotent Supabase 마이그레이션 2. 검증 — drift CI 1주 0-error + 수동 dump diff 3. cutover — prod SKIP_DB_MIGRATIONS=1 4. cleanup — migration/ 디렉토리 + main.rs Migrator + env 제거 본 PR 의 wiki:lint 는 #375 의 frontmatter 정합 commit (81aeabe5) 의존 (e2e-hardening-reprioritization-design.md 의 기존 에러). 본 PR 머지 후 e2e 파일 에러는 #375 머지로 해소됨. Co-Authored-By: Claude Opus 4.7 (1M context) --- ...-374-seaorm-supabase-integration-design.md | 186 ++++++++++++++++++ 1 file changed, 186 insertions(+) create mode 100644 docs/superpowers/specs/2026-04-30-374-seaorm-supabase-integration-design.md diff --git a/docs/superpowers/specs/2026-04-30-374-seaorm-supabase-integration-design.md b/docs/superpowers/specs/2026-04-30-374-seaorm-supabase-integration-design.md new file mode 100644 index 00000000..60359e98 --- /dev/null +++ b/docs/superpowers/specs/2026-04-30-374-seaorm-supabase-integration-design.md @@ -0,0 +1,186 @@ +--- +title: "[Epic #374] SeaORM ↔ Supabase 마이그레이션 통합 분해안" +owner: human +status: draft +updated: 2026-04-30 +tags: [db, ops, architecture] +--- + +# [Epic #374] SeaORM ↔ Supabase 마이그레이션 통합 분해안 + +## 한 줄 요약 + +dev/prod 가 두 마이그레이션 시스템 (SeaORM 62개 vs Supabase CLI 6개) 을 공존시키는 현 상태가 drift 의 근본 원인이다. **Option B (SeaORM 폐기 + Supabase CLI 단독화)** 를 채택하고, **prod 스키마 dump → 새 baseline 마이그레이션 → SeaORM 코드 제거** 의 4 phase 로 분해한다. 본 spec 은 epic 의 sub-issue 후보를 정의하며, 실행은 #371/#372/#373 이 안정화되고 nightly drift CI (#373) 가 1주 이상 green 인 후 시작한다. + +## 배경 + +### 현 상태 정량화 (2026-04-30 기준) + +| 시스템 | 파일 수 | 적용 환경 | 비고 | +| -------------------------- | -------------------------- | ---------------------------------------------- | ------------------------------------------------------------------------------- | +| Supabase CLI 마이그레이션 | 6 (`supabase/migrations/`) | dev (SOT) + prod (#282 이후) | 신규 변경의 1차 SOT. 2026-04-09 baseline 부터 작성 | +| SeaORM 마이그레이션 (Rust) | 62 (`migration/src/*.rs`) | dev `SKIP_DB_MIGRATIONS=1` 로 skip / prod 실행 | 그중 43개가 DDL (create_table / alter_table). RawSql 미사용 → SeaORM-native DDL | +| SeaORM SQL 인덱스 | 3 (`migration/sql/*.sql`) | prod 실행 | `006_indexes_and_constraints.sql` 가 #372 drift 의 진원지 | +| TypeScript 상수 | n/a | n/a | enum/허용값/JSON 구조의 앱 측 SOT (`MAGAZINE_STATUSES` 등) | + +### 왜 통합인가 + +1. **반복 drift**: SeaORM 단독 변경이 prod 에 흘러들어 dev/Supabase CLI/TS 상수와 어긋난다 (#372 사례). nightly drift CI (#373) 가 사후 감지하지만, 근본 원인은 두 시스템 공존. +2. **운영 부담**: 새 컬럼 하나 추가 시 두 곳에 작성 → 한쪽 누락 위험 + 코드 리뷰 부담. +3. **이미 dev 는 단일 시스템**: `SKIP_DB_MIGRATIONS=1` 로 SeaORM 을 우회. prod 만 두 시스템 공존. 즉 통합은 prod 만 dev 의 모델로 정렬하는 작업. + +### 옵션 비교 + +| | Option A: 두 시스템 동기 유지 | Option B: SeaORM 폐기 (채택) | +| ----------------------------------------- | ----------------------------------------------------------- | ----------------------------------------------- | +| **변경 비용 (장기)** | 매 변경마다 두 곳 + idempotent 보장 | 한 곳만 | +| **drift 위험** | 사람이 두 곳 동기화 보장 → drift 재발 | 단일 SOT → 구조적 차단 | +| **실행 비용 (1회)** | 62 SeaORM idempotent 화 + 검증 | prod schema dump → baseline + SeaORM 제거 | +| **SKIP_DB_MIGRATIONS 운명** | 유지 (dev 만 skip) | 삭제 (env 자체 제거) | +| **packages/api-server 마이그레이션 코드** | 유지, 확대 | `migration/` 디렉토리 통째 제거 | +| **prod 적용 메커니즘** | api-server 부팅 시 SeaORM + 별도 Supabase CLI push | Supabase CLI push 단일화 | +| **추천 근거** | 두 곳 유지 = 사람의 규율에 의존. 실패 사례 이미 다수 (#372) | 구조적으로 drift 발생 불가능. 1회 비용으로 해결 | + +### 비-목표 + +- 본 spec 안에서 staging 환경 정의를 강제하지 않는다 (Phase 2 검증을 dev 컨테이너로 갈음). +- SeaORM Entity 코드 (`entities/`) 는 통합 대상이 아니다 — 그건 ORM 레이어이고, 마이그레이션과 별개로 유지된다. +- Phase 1 의 baseline 작성 자체에서 스키마 디자인 변경은 하지 않는다 — 현 prod 의 그대로를 SQL 로 동결한다. + +## 채택 방향 — Option B 의 4 Phase 분해 + +``` +Phase 1 (baseline) Phase 2 (검증) Phase 3 (cutover) Phase 4 (cleanup) +───────────────── ───────────────── ───────────────── ───────────────── +prod schema dump dev clean DB + prod 에서 SKIP=1 set migration/ 삭제 +→ supabase/migrations/ Supabase migrations → → 다음 변경부터 단일 SOT → main.rs Migrator 제거 + _baseline.sql drift CI green 1주 → 마지막 SeaORM 적용 → SKIP_DB_MIGRATIONS env 제거 + → release noted 으로 알림 → docs 재작성 +``` + +### Phase 1 — baseline 마이그레이션 작성 (Sub-issue 후보 #374-1) + +**목표**: 현 prod 스키마 전체를 1개 idempotent Supabase 마이그레이션으로 옮긴다. + +**산출물**: + +- `supabase/migrations/_baseline_seaorm_consolidation.sql` (1 파일) +- `pg_dump --schema-only --schema=public` 결과를 idempotent (`CREATE TABLE IF NOT EXISTS`, `DO $$ ... $$`) 로 변환한 SQL +- baseline 작성 스크립트 (`scripts/dump-baseline.sh`) — 재생성 가능 + +**Acceptance**: + +- [ ] dev clean DB (`supabase db reset --no-seed`) → baseline 적용 후 `\d+` 결과가 prod `\d+` 와 동일 (drift CI 0 errors) +- [ ] 기존 6개 supabase/migrations 가 baseline 위에서 모두 idempotent 적용 가능 (no-op 또는 valid follow-up) +- [ ] baseline 자체가 idempotent (재적용 시 에러 없이 통과) + +**예상 공수**: 3-5 일 + +**위험**: + +- pg_dump 출력의 statement 순서가 의존성을 반영하지 못해 재적용 시 실패 가능 → 수동 reorder 필요 +- 기존 6 supabase/migrations 와 baseline 간 중복 객체 → baseline 작성 시점에 6개 마이그레이션 합쳐 1개로 만드는 것도 검토 + +### Phase 2 — 검증 (Sub-issue 후보 #374-2) + +**목표**: nightly drift CI (#373) 가 1주 이상 0 error 인 상태에서, baseline 적용한 dev DB 가 prod 와 100% 일치함을 보인다. + +**산출물**: + +- drift CI 1주간 매일 0 error 기록 (artifact 보관) +- prod ↔ dev (baseline 만 적용) `pg_dump --schema-only` diff = 0 라인 (allowlist 적용 후) +- (선택) PR check 도입 — `supabase/migrations` 변경 PR 마다 dev 컨테이너에 적용 후 prod 와 schema diff. **본 PR 은 Phase 2 의 산출물로 묶을 수 있음** + +**Acceptance**: + +- [ ] drift CI 7일 연속 green +- [ ] 수동 1회 검증: dev 에서 `supabase db reset --no-seed && (마이그레이션 적용)` 후 dump 와 prod dump 비교 = 0 의미 있는 차이 +- [ ] (선택) PR check 도입 시 PR 단계에서 drift 자동 차단 동작 확인 + +**예상 공수**: 1주 (대기 + 1-2일 검증 작업) + +**위험**: + +- 1주 대기 동안 새 변경이 들어가면서 drift 재발 가능 → 본 phase 는 cutover 직전에 짧게 (3일 등) 단축 가능 +- PR check 의 false positive 가 빈번해지면 개발 흐름 차단 → allowlist 운영 정책 필요 + +### Phase 3 — prod cutover (Sub-issue 후보 #374-3) + +**목표**: prod 에서 SeaORM 마이그레이션을 더 이상 실행하지 않도록 전환. + +**산출물**: + +- prod 환경의 `SKIP_DB_MIGRATIONS=1` 설정 (Vercel / api-server 배포 환경) +- 마지막 SeaORM 마이그레이션 release notes 에 \"이후 SeaORM 마이그레이션 추가 금지\" 안내 +- `docs/DATABASE-MIGRATIONS.md` 의 prod 행을 \"Supabase CLI 단일\" 로 갱신 (단계적) + +**Acceptance**: + +- [ ] prod 재배포 후 api-server 로그에 \`SKIP_DB_MIGRATIONS=1 set\` 메시지 확인 +- [ ] 다음 schema 변경이 supabase/migrations 만으로 적용 가능 (1개 PR 로 검증) +- [ ] drift CI 가 cutover 후에도 green 유지 + +**예상 공수**: 0.5 일 (env 변경 + 검증) + +**위험**: + +- prod env 변경은 인프라 작업 → ops 협력 필요 +- 롤백 시점에 `SKIP_DB_MIGRATIONS=0` 으로 되돌리고 SeaORM 재적용해도 안전한지 확인 (Phase 1/2 가 idempotent 여야 가능) + +### Phase 4 — cleanup (Sub-issue 후보 #374-4) + +**목표**: SeaORM 마이그레이션 코드를 통째 제거 + docs 재작성. + +**산출물**: + +- `packages/api-server/migration/` 디렉토리 삭제 +- `packages/api-server/src/main.rs` 의 `Migrator::up` 호출 제거 + `SKIP_DB_MIGRATIONS` env 분기 제거 +- `Cargo.toml` 에서 `migration` workspace member 제거 +- `docs/DATABASE-MIGRATIONS.md` 단일 시스템 모델로 재작성 +- `docs/database/operating-model.md` 의 SeaORM 경고 / SKIP_DB_MIGRATIONS 언급 모두 제거 +- `packages/api-server/AGENT.md`, `packages/api-server/CLAUDE.md` 동일 갱신 +- `scripts/local-env-sync.sh` 에서 `SKIP_DB_MIGRATIONS=1` 라인 제거 + +**Acceptance**: + +- [ ] `cargo build` 통과 (workspace 의존성 정리) +- [ ] dev 환경에서 api-server 부팅 시 SeaORM 관련 로그 0 +- [ ] `SKIP_DB_MIGRATIONS` 가 repo 어디에도 등장하지 않음 (`grep` 0) +- [ ] `docs/database/operating-model.md` 의 마이그레이션 시스템 표가 \"Supabase CLI\" 1행으로 축소 + +**예상 공수**: 2-3 일 + +**위험**: + +- SeaORM Entity 코드 (`entities/`) 가 마이그레이션 디렉토리에 의존하지 않는지 재확인 필요 +- 기존 SeaORM 마이그레이션이 만든 trigger / function 의 정의가 entities 에서 참조되는지 확인 (예: Generated Column) + +## 선결 조건 + +- [x] **#371 운영 모델 정착** — PR #375 머지 +- [x] **#372 'failed' drift 정합화** — PR #376 머지 +- [ ] **#373 nightly drift CI 안정화** — PR #378 머지 + 1주 0-error 운영 +- [ ] **(선택) staging 환경 정의** — Phase 2 검증을 dev 컨테이너로 갈음하는 경우 불필요. 별도 staging 도입은 본 epic 외 작업. + +## Risk 종합 + +| Risk | 완화 | +| ---------------------------------------------- | ------------------------------------------------------------------------------------------------ | +| Phase 1 baseline 의 statement 순서 의존성 깨짐 | pg_dump 출력 후 `psql` 로 재적용 검증 + 깨지면 수동 reorder. 도구 (`migra`, `apgdiff`) 도입 검토 | +| Phase 2 의 1주 대기 동안 새 drift 발생 | 본 phase 진행 중에는 schema 변경 PR 모라토리엄 또는 단축 (3일) | +| Phase 3 prod cutover 의 ops 충돌 | 운영팀과 사전 일정 협의. 롤백 절차 (env 복원 + Migrator 재실행) 문서화 | +| Phase 4 cleanup 의 hidden 의존성 | `cargo check` + `cargo test` + dev 통합 검증. SeaORM Entity 만 남기고 마이그레이션 SQL 만 제거 | +| nightly drift CI false positive 누적 | allowlist 정책 (3개월 시한, PR description 사유) 으로 관리 | + +## 의사결정 (제안) + +1. **Option B 채택** — 두 시스템 유지의 운영 비용이 1회 통합 비용을 상회. +2. **Phase 분해 위와 같이** — sub-issue 4개로 트래킹. +3. **시작 시점**: nightly drift CI (#378) 머지 + 1주 0-error 운영 검증 후. 그 전까지는 본 spec 만 head 에 두고 epic 진행 보류. +4. **staging 환경**: 본 epic 의 선결 조건이 아님. Phase 2 검증은 dev 컨테이너 + drift CI 로 갈음. + +## 후속 + +- 본 spec PR 머지 후 sub-issue 4개 (#374-1 ~ #374-4) 등록 +- #373 (nightly drift CI) 1주 0-error 검증 후 Phase 1 착수 +- Phase 1 PR description 에서 본 spec 을 \"상위 문서\" 로 cross-reference