|
| 1 | +# staging 環境の failed migration 修復計画 |
| 2 | + |
| 3 | +## 概要 |
| 4 | + |
| 5 | +`20260406112057_add_vote_grade_check_constraints` migration が staging DB で P3009 エラー(失敗中状態)で止まっており、以降の migration deploy が全てブロックされている状態を GHA 経由で修復する。 |
| 6 | + |
| 7 | +## 根本原因 |
| 8 | + |
| 9 | +migration.sql 内のテーブル名が Pascal Case クォート形式(例: `"VotedGradeCounter"`)で記述されているが、`schema.prisma` の `@@map` により実際の PostgreSQL テーブル名は小文字(`votedgradecounter`)。 |
| 10 | + |
| 11 | +```sql |
| 12 | +-- 誤(失敗した migration) |
| 13 | +ALTER TABLE "VotedGradeCounter" ADD CONSTRAINT ... |
| 14 | + |
| 15 | +-- 正(@@map に合わせた正しい記述) |
| 16 | +ALTER TABLE "votedgradecounter" ADD CONSTRAINT ... |
| 17 | +``` |
| 18 | + |
| 19 | +PostgreSQL はクォート付きの識別子を大文字小文字区別で処理するため、`"VotedGradeCounter"` は存在しないテーブルを参照して ALTER TABLE が失敗。`_prisma_migrations` に `finished_at = NULL` のレコードが残り P3009 状態となった。 |
| 20 | + |
| 21 | +## 他 AI 提案のレビュー・却下理由 |
| 22 | + |
| 23 | +| 提案内容 | 評価 | 理由 | |
| 24 | +| ---------------------------------------- | ----------------- | --------------------------------------------------------------------------------------------------------------------- | |
| 25 | +| `migrate resolve --rolled-back` の使用 | ✅ 正しい | P3009 を解消する唯一の手段 | |
| 26 | +| 同名 `migration.sql` を修正して再 deploy | ❌ **機能しない** | `--rolled-back` にマークされた migration は `migrate deploy` が永久にスキップする。同ファイルを修正しても実行されない | |
| 27 | + |
| 28 | +**正しいアプローチ:** rolled-back 解消後は、**新しいタイムスタンプで新 migration ファイルを作成**して deploy する必要がある。 |
| 29 | + |
| 30 | +## 設計方針・却下した代替案 |
| 31 | + |
| 32 | +### 採用: workflow_dispatch + 新 migration 作成 |
| 33 | + |
| 34 | +- GHA の `workflow_dispatch` トリガーで `migrate resolve` を一度だけ実行 |
| 35 | +- 旧 migration ファイルを git から削除(ローカル開発環境で `migrate dev` が失敗しないよう) |
| 36 | +- 新タイムスタンプで正しいテーブル名の migration を作成 |
| 37 | + |
| 38 | +### 却下: ci.yml への一時追記 |
| 39 | + |
| 40 | +- `resolve` ステップを既存 `preview` ジョブに追記する案 |
| 41 | +- 問題: すでに成功した将来の deploy でも毎回 resolve コマンドが走る(冪等ではあるが冗長) |
| 42 | +- `workflow_dispatch` の方がスコープが明確で安全 |
| 43 | + |
| 44 | +### 却下: DB への直接接続 |
| 45 | + |
| 46 | +- staging DB に直接アクセスして `_prisma_migrations` を操作する案 |
| 47 | +- 方針: GHA 経由でのみ DB を操作するため却下 |
| 48 | + |
| 49 | +## 実装フェーズ |
| 50 | + |
| 51 | +### Phase 1: 変更を staging へ push(GHA に workflow_dispatch を公開) |
| 52 | + |
| 53 | +**変更ファイル:** |
| 54 | + |
| 55 | +| 操作 | パス | |
| 56 | +| ---- | --------------------------------------------------------------------------------- | |
| 57 | +| 追加 | `.github/workflows/resolve-migration.yml` | |
| 58 | +| 削除 | `prisma/migrations/20260406112057_add_vote_grade_check_constraints/` | |
| 59 | +| 追加 | `prisma/migrations/20260409000000_fix_vote_grade_check_constraints/migration.sql` | |
| 60 | + |
| 61 | +**`.github/workflows/resolve-migration.yml` 内容:** |
| 62 | + |
| 63 | +```yaml |
| 64 | +name: Resolve Failed Migration (one-time use) |
| 65 | +on: |
| 66 | + workflow_dispatch: |
| 67 | + |
| 68 | +jobs: |
| 69 | + resolve: |
| 70 | + runs-on: ubuntu-latest |
| 71 | + steps: |
| 72 | + - uses: actions/checkout@v6 |
| 73 | + - uses: pnpm/action-setup@v5 |
| 74 | + with: |
| 75 | + package_json_file: package.json |
| 76 | + run_install: false |
| 77 | + - uses: actions/setup-node@v6 |
| 78 | + with: |
| 79 | + node-version: 24 |
| 80 | + cache: 'pnpm' |
| 81 | + cache-dependency-path: pnpm-lock.yaml |
| 82 | + - run: pnpm install |
| 83 | + - name: Resolve failed migration in staging DB |
| 84 | + run: | |
| 85 | + pnpm exec prisma migrate resolve \ |
| 86 | + --rolled-back 20260406112057_add_vote_grade_check_constraints |
| 87 | + env: |
| 88 | + DATABASE_URL: ${{ secrets.PREVIEW_DATABASE_URL }} |
| 89 | + DIRECT_URL: ${{ secrets.PREVIEW_DIRECT_URL }} |
| 90 | +``` |
| 91 | +
|
| 92 | +**新 migration SQL:** |
| 93 | +
|
| 94 | +```sql |
| 95 | +-- VotedGradeCounter.count must never go negative (application guard + DB last line of defense) |
| 96 | +ALTER TABLE "votedgradecounter" ADD CONSTRAINT "votedgradecounter_count_non_negative" |
| 97 | + CHECK (count >= 0); |
| 98 | + |
| 99 | +-- VoteGrade.grade must not be PENDING (only non-pending grades are valid votes) |
| 100 | +ALTER TABLE "votegrade" ADD CONSTRAINT "votegrade_grade_not_pending" |
| 101 | + CHECK (grade != 'PENDING'); |
| 102 | + |
| 103 | +-- VotedGradeCounter.grade must not be PENDING |
| 104 | +ALTER TABLE "votedgradecounter" ADD CONSTRAINT "votedgradecounter_grade_not_pending" |
| 105 | + CHECK (grade != 'PENDING'); |
| 106 | + |
| 107 | +-- VotedGradeStatistics.grade must not be PENDING (median must be a real grade) |
| 108 | +ALTER TABLE "votedgradestatistics" ADD CONSTRAINT "votedgradestatistics_grade_not_pending" |
| 109 | + CHECK (grade != 'PENDING'); |
| 110 | +``` |
| 111 | + |
| 112 | +> この push で preview ジョブが起動するが、staging DB はまだ stuck 状態のため `migrate deploy` は P3009 で失敗する。これは想定内。 |
| 113 | +
|
| 114 | +### Phase 2: workflow_dispatch を手動トリガー |
| 115 | + |
| 116 | +GitHub Actions UI から操作: |
| 117 | + |
| 118 | +1. リポジトリの Actions タブを開く |
| 119 | +2. "Resolve Failed Migration (one-time use)" を選択 |
| 120 | +3. "Run workflow" → **staging ブランチ**を選択して実行 |
| 121 | + |
| 122 | +これにより staging DB の `_prisma_migrations` の対象レコードに `rolled_back_at` がセットされ、P3009 が解消される。 |
| 123 | + |
| 124 | +### Phase 3: 失敗した preview ジョブを再実行 |
| 125 | + |
| 126 | +GitHub Actions UI から Phase 1 の push で失敗した preview ジョブを "Re-run jobs" で再実行。今度は `migrate deploy` が P3009 なしで通り、新 migration `20260409000000_fix_vote_grade_check_constraints` が適用される。 |
| 127 | + |
| 128 | +## 検証 |
| 129 | + |
| 130 | +| タイミング | 確認内容 | |
| 131 | +| -------------- | ------------------------------------------------------------ | |
| 132 | +| Phase 2 完了後 | `workflow_dispatch` ジョブの終了コードが 0 | |
| 133 | +| Phase 3 完了後 | GHA の "Apply all pending migrations" ステップが成功 | |
| 134 | +| Phase 3 完了後 | staging の Vercel デプロイが完了 | |
| 135 | +| Phase 3 完了後 | staging アプリで投票機能が正常動作(CHECK 制約が DB に存在) | |
| 136 | + |
| 137 | +## ローカル開発環境への影響 |
| 138 | + |
| 139 | +旧 migration ファイルを git から削除することで、開発者が `git pull` 後に `migrate dev` を実行しても旧 SQL は実行されず、代わりに新 migration が適用される。 |
| 140 | + |
| 141 | +ただし、**旧 migration をすでにローカル DB に適用しようとして failed 状態にしている開発者**は別途対応が必要: |
| 142 | + |
| 143 | +```bash |
| 144 | +# ローカル DB の stuck 状態を解消 |
| 145 | +pnpm exec prisma migrate resolve --rolled-back 20260406112057_add_vote_grade_check_constraints |
| 146 | + |
| 147 | +# その後 migrate dev を実行 |
| 148 | +pnpm exec prisma migrate dev |
| 149 | +``` |
| 150 | + |
| 151 | +## 事後処理 |
| 152 | + |
| 153 | +`resolve-migration.yml` は使い捨て。fix 確認後に削除 PR を出してもよい(再実行しても冪等なので必須ではない)。 |
0 commit comments