Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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 제거
<ts>_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/<ts>_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
Loading