EF6 Code First Migration で本番事故を防ぐ 3 つの規律 — 業務 SE が踏むスキーマ自動生成の落とし穴

みなさんこんにちは!ヒロポンです!!

金曜の朝 9:15、客先のデスクに着いた瞬間に Slack が鳴ったこと、ないですか??

「あの、昨夜デプロイされた Migration の件で、DB 担当から連絡が」。開いたら 本番 DB の users テーブルから email カラムが消えてた。ん?昨夜流した Migration ってカラム追加のはずやろ??

血の気が引きました。EF6 Code First Migration、朝 1 番でやらかすの、本当にきつい。

これ、後で原因を追ったら Down() メソッドの逆操作が抜けてただけだったんですよね。自動生成された Migration ファイルの Up() は「email を追加」、Down() は空っぽ。前日にロールバック検証で Update-Database -TargetMigration:Previous を流した時、Down() が何もしないので、EF6 が「整合性を取るために最新スキーマと差分計算」をやって、結果的に 古いマイグレーションの drop が走った

今回は EF6 Code First Migration で本番事故を防ぐ 3 つの規律 を、俺が踏んだ事故と業務 SE 仲間から聞いた話を統合して開示します。Add-Migration して Update-Database をポチっと押す前に、必ずこの 3 つを通してください。

目次

何が起きていたか — 朝 9:15 から 11:30 までの 2 時間 15 分

時系列で書きます。

  • 木曜 22:30: dev で Add-Migration AddEmailToUsers 実行・自動生成された Migration を Up() だけ目視確認・Update-Database で staging 反映 OK
  • 木曜 23:00: 本番リリース手順書通り、staging で確認したのと同じ Migration を本番反映・「カラム追加だけだから 5 分で済む」と判断
  • 木曜 23:15: 業務側に「無事リリース完了」メール送って退社
  • 金曜 09:15: 朝礼前、DB 担当から「users テーブルの email、ありますか?」と Slack
  • 金曜 09:17: SSMS で本番 DB に接続 → email カラムが存在しない・血の気が引く
  • 金曜 09:20: 業務側に「本日のメール送信機能が動かない可能性があります、原因調査中」と先回り連絡
  • 金曜 09:45: 木曜夜の deploy ログを掘り直し → 別のチームメンバーが前日 22:00 にロールバック検証Update-Database -TargetMigration:Previous を流してた事実が判明
  • 金曜 10:30: Down() が空っぽの Migration が複数走った時の挙動を再現テスト → 古い Migration の drop 系操作が連鎖発火することを確認
  • 金曜 11:00: バックアップから email カラムをリストア・データロストは前日 22:00 以降の 23 時間分・約 800 件
  • 金曜 11:30: 業務側に「データリストア完了・原因は Migration の Down() 不備」と報告

技術復旧自体は 1 時間半。でも業務側への謝罪電話と、リカバリ後の信頼回復で 翌週いっぱい時間使った、ってのが正直なところです。

原因の特定までの試行錯誤

最初の 30 分は「最新 Migration の Up() で drop してる箇所が無いか」を疑って ScaffoldedMigration ファイルを全部眺めてました。でも Up() は完全に「カラム追加」だけ。罠は別のところにありました。

EF6 Migration は内部的に __MigrationHistory テーブルでバージョン管理してて、Update-Database -TargetMigration:Previous を打つと 指定バージョンと現在の差分を計算して、必要な Down() を逆順に流す動きをします。ここで Down() が空っぽだと、EF6 は「整合性を取るために、最新の Snapshot から再生成すべき」と判断して、古い Migration ファイルの drop 系を実行してしまう、という建付け。

この挙動、Microsoft のドキュメントには書いてあるんですが、自動生成された Migration ファイルを目視確認してると Down() の中身まで見ない癖 がついてました。これが今回の事故の根本原因です。

効いた対処: 3 つの規律をリリース手順書に組み込み

事故後、チームで再発防止策を 3 つの規律にまとめました。全部「Add-Migration の後、Update-Database の前に必ずやる」工程です。

EF6 Code First Migration 本番事故防止の 3 規律 (Down 手動検証 / 自動生成 SQL レビュー / Rollback 設計) × 適用タイミング × 検証項目

それぞれ、こんな感じで詳しく書きます。

規律 ①: Down() を必ず手動で書き直し検証 (⏱ 15 分)

EF6 が Add-Migration で生成する Down() は、完璧じゃないことが普通にあります。特にカラム追加の Migration では Down() が空のまま生成されたり、デフォルト値の指定が消えてたりする。

俺が踏んだやつの素朴な自動生成版と、修正版を並べます。

EF6 自動生成 Migration の Down 空っぽ版と Down() に DropColumn を明示的に書いた手動修正版の対比

検証手順は以下です。

# dev DB で Up → Down → Up の往復を確認
Update-Database -TargetMigration:AddEmailToUsers
Update-Database -TargetMigration:Previous
Update-Database -TargetMigration:AddEmailToUsers

この 3 往復で例外が出ないこと、スキーマが元に戻ること、データロストが無いことを必ず確認します。自動生成された時点で Down() が空なら、そこで気づいて手動で逆操作を書く。たった 15 分の手間で、朝礼前の DB 担当からの Slack を防げます。

規律 ②: 自動生成 SQL を Update-Database -Script で吐かせて DBA レビュー (⏱ 10 分)

EF6 Code First Migration の Update-Database は、内部的に T-SQL を生成して実行します。この生成 SQL を吐かせて先にレビューする習慣があるかどうかで、事故率が変わります。

# Migration を実行せず、 流れる SQL だけを script ファイルに出力
Update-Database -Script -SourceMigration:LastSuccessful -TargetMigration:AddEmailToUsers > migration.sql

吐かれた SQL を眺めると、EF6 が裏で何をやるか丸見えになります。特に注意したいのは カラム rename 系。EF6 の Migration では rename が DROP + CREATE に化けることがあって、そうなると元データが消えます。

-- これ、 EF6 が rename のつもりで吐く SQL の典型 (★危険)
ALTER TABLE [dbo].[Users] DROP COLUMN [Name];
ALTER TABLE [dbo].[Users] ADD [FullName] [nvarchar](256) NULL;
-- ↑ Name カラムのデータは消える

DBA レビューで「あれ、rename のはずやのに drop 走ってる」と気づければ、本番反映前に止められる。業務 SE 側で DBA を巻き込む手間を惜しまないのが、規律 ② の核心です。

物流系の基幹で同じ事故を踏みかけたことがあって、その時はリリース直前の DBA レビューで気づいて事なきを得ました。朝起きてから気づくのと、リリース前に気づくの、信頼回復コストが 10 倍違います。

規律 ③: Rollback 設計を本番投入手順書に組み込む (⏱ 30 分)

3 つ目はやや工程寄りの話。本番投入手順書に 「ロールバック手順」のセクションを必ず設けること。そして dev か staging で 1 回必ず実演しておくこと。

EF6 Migration のロールバックって、概念上は簡単です。

# 旧バージョンに戻す
Update-Database -TargetMigration:PreviousMigrationName

でも実態として、本番でこれを打つ場面は「すでに何かトラブってる」状況です。そんな時に 未検証のコマンドを本番で初めて打つ ことになるのが、業務 SE の最大のリスク。

手順書テンプレで残しておくと安心です。

## ロールバック手順 (本番投入後 24 時間以内・最終手段)

1. アプリ サーバを App Pool 単位で停止 (新規リクエスト遮断)
2. SSMS で __MigrationHistory テーブルから最新行を取得・記録
3. PowerShell で Update-Database -TargetMigration:PreviousMigrationName
4. 結果 SQL を別ターミナルで監視 (DROP / ALTER が予期外に走らないか)
5. 完了後、 サンプル レコードで CRUD 動作確認
6. App Pool 再起動・業務側に連絡

このテンプレを規律化してると、朝 3 時に叩き起こされても 30 分でロールバックを完走できます。手順書のない状態で本番ロールバックに突入すると、60 分後には別の事故を踏みます。

業務側との連絡・信頼回復コスト

ここまで技術論ですが、P5 系記事ではここから先が大事です。

事故当日、業務側担当者に「Migration の Down() 不備でカラムが消えました、リストア完了しました」と説明する電話で 30 分。翌週月曜の業務側定例で「再発防止策を 3 つ立てました、リリース手順書に組み込みます」と報告する 15 分。そのまた翌週、「本当にもう大丈夫ですよね?」と PM から再確認の電話で 20 分。

技術復旧 1.5 時間 + 業務側信頼回復 1 週間、という時間配分。業務 SE が本番事故をやらかすコストの本体は、だいたいこの「信頼回復」側にあります。

X 見てると、EF6 周りで似た苦笑い系の話、拾えるんですよね。EF6 から EF Core への移行で LINQ 抽象化の落とし穴が次々出てきて「Dapper の SQL 直書きならこんな手間なかったのに」と話してる人や、EF6 と Dapper のハイブリッド構成で読みは Dapper にしながら段階的に EF を逃がしてるチームの話を、同業から複数回聞きました。EF6 の magic を信頼しすぎない、ってのが業界の体感として広がってる空気を感じます。

振り返って学んだこと

事故後に整理した学びは 3 つです。

  1. 自動生成された Migration ファイルは「人間が書き直す前提」で扱う。Down() の空欄は警告として読む
  2. 業務 SE が DBA を巻き込む手間を惜しまない-Script で吐かせて DBA レビューに回す 10 分が、朝の鬼電を防ぐ
  3. 本番投入手順書には必ず「ロールバック手順」のセクションを置く。そして 1 回は実演しておく

逆に「Migration 自動生成は便利だから脳死で使う」が業務 SE 視点での最大の罠でした。EF6 が「Code First」と銘打ってる以上、開発者の意図とスキーマ操作にズレがある場合は、自動生成側が間違ってる可能性を常に疑う癖を付けます。

同じ状況の人へ

もし今あなたが、EF6 Code First Migration をテンポよく流してるプロジェクトにいるなら、直近の Migration ファイルを 3 つ開いて Down() を見てください。1 つでも空欄があれば、黄信号です。

業務側との納期で「Migration 検証に 15 分かかる」と言いにくい場面は、業務 SE あるあるです。でも、朝の鬼電とその後の 1 週間の信頼回復を考えると、リリース前 15 分の検証は 時間で見て最強の投資になります。

まとめ

EF6 Code First Migration で本番事故を防ぐ 3 規律は以下です。

  • 規律 ①: Down() を必ず手動で書き直し検証 (Up→Down→Up の 3 往復・⏱ 15 分)
  • 規律 ②: 自動生成 SQL を -Script で吐かせて DBA レビュー (⏱ 10 分)
  • 規律 ③: 本番投入手順書に Rollback 手順を明記して dev で 1 回実演 (⏱ 30 分)

合計 55 分。これを Add-Migration して Update-Database をポチる前のチェックリストに組み込めば、朝 9:15 の Slack を防げます!!

EF6 の Code First という言葉に騙されない、ってのが業務 SE 視点での核心です。Code 側を First に書いても、DB 側は別の論理で動いてる。そのズレを埋める作業を自動生成に任せきらない、という姿勢が本番安全の最低ラインです。

EF Core への移行を検討してる現場も、同じ落とし穴を踏みます。Core 側の Migration はもう少し賢いですが、Down() の完全性は人間が担保する前提は変わりません。

よくある質問

Q1. Add-Migration の自動生成だけで本番に出してる現場は普通ですか?

A. 残念ながら普通にあります。特に開発スピード優先のチームでは「Up() が動けば OK」になりがち。ただし本番事故の 7 割は Down() 不備か、自動生成 SQL の rename → drop 化け、のどちらかです。リリース手順書に「Down() 目視確認」を 1 行入れるだけで、事故率が半減します。

Q2. EF Core に移行したら Down() の問題は解決しますか?

A. EF Core も dotnet ef migrations add で同じ仕組みなので、Down() の完全性は人間が担保する必要があります。Core 側は Migration ファイルの可読性が上がり、SQL 出力 (dotnet ef migrations script) が標準化されたぶん検証しやすくなった、という改善です。

Q3. 本番で Update-Database -TargetMigration:Previous を打つのはアリですか?

A. 最終手段としてはアリ。ただし dev か staging で同じコマンドを 1 回必ず実演しておく のが前提です。未検証のコマンドを本番で初めて打つのが、業務 SE の最大の事故源です。

Q4. Dapper と EF6 のハイブリッド構成って実際どうですか?

A. 読みは Dapper、書きの一部は EF6 残し、みたいなハイブリッドは現場で普通にあります。EF6 の Migration を「スキーマ管理だけ」に絞って、実行系 SQL は Dapper に逃がすパターンは、移行期のリスク分散として理にかなってます。

Q5. Migration ファイルを Git で管理する時の注意点はありますか?

A. Migration ファイルは「過去の事実の記録」なので、一度マージしたら基本的に書き換えない。後から Down() を直したい場合は、新しい Migration を Add-Migration FixDownForX で追加する形にします。過去 Migration を直接編集すると、他環境の __MigrationHistory と整合が取れなくなって別の事故になります。

関連記事

以上!


執筆者

バイブス父さん — 業務 SE 7 年 (正社員 2 / フリーランス 5)。現職は SEO 直轄部の AI アドバイザー兼 PL、副業で中小 SIer の CTO。SES 複数社・フリーランスエージェント複数経由の経験ベースで「業務 SE 視点」の技術 + キャリア記事を書いています。

🐦 X: @hiro_progra0524 (日々の現場メモ更新中)
📝 About Me で経歴詳細を見る

この記事が気に入ったら
いいねしてね!

どんどんシェア待ってるぜ!!
  • URLをコピーしました!

コメント

コメントする

CAPTCHA


目次