AIが作ったDB変更、テストは成功した。開場前に座席データが消えた
金曜16時18分。小さな劇場では、夜公演の照明確認が始まっていた。
受付で使う予約システムには、1,186人分の座席データが入っている。これまで席種は seat_type という列に「一般」「招待」「車いす席」と保存していた。画面を分かりやすくするため、担当エンジニアは列名を seat_category へ変える作業をAIへ頼んだ。
AIはスキーマを直し、マイグレーションを生成し、テストも追加した。CIはすべて成功。PRの説明には「既存の席種を新しい項目名へ移行」と書かれていた。
その日は、別の不具合対応も重なっていた。担当者はスキーマの差分とテスト結果を見て、生成されたSQLは流し読みした。AIにここまで任せられると劇場スタッフへ話したばかりで、予定より遅れているとは言いづらかった。
16時31分、ステージングへの適用は成功した。ところが、受付画面を開くと全予約の席種が空欄になっていた。
マイグレーションが行っていたのは、列名の変更ではなかった。
ALTER TABLE reservations DROP COLUMN seat_type;
ALTER TABLE reservations ADD COLUMN seat_category TEXT; 古い列を消し、空の新しい列を作っていた。開発用DBには数件の作り直せるデータしかなく、テストも新規予約だけを確認していた。本番に近い1,186件の既存予約は、確認の外にあった。
劇場スタッフが「席種が見えないと、開場できません」と言った。担当者は喉が詰まり、すぐ返事ができなかった。
AIが作ったとは言い訳にならない。自分がマージした。今さら分からないと言ったら、任せてもらえなくなるかもしれない。
そう考えると、まずコードを元へ戻し、データはバックアップから一人で直してから報告したくなった。あと29分で開場する。焦るほど、誰かを呼ぶより自分で端末を触る方が早く見えた😨
でも、コードを戻しても、消した列の中身まで自動で戻るとは限らない。確認せずに次のSQLを流せば、残っている復旧材料まで変える可能性がある。
ここで止めるべきなのはAIの利用ではない。「テスト成功」をDB変更の確認終了にしてしまう流れだ。
スキーマの完成形だけでは、途中で何が起きるか分からない
スキーマには、変更後のテーブルがどうあるべきかが書かれている。マイグレーションには、今のデータベースをそこへ動かすために、何をどの順で実行するかが書かれている。
この二つは同じではない。
Prismaの公式ドキュメントでも、フィールド名を変えただけで、生成結果が「古い列を削除し、新しい列を追加する」形になり、既存データを失う場合があると説明されている。データを保つには、生成されたマイグレーションを適用前に編集し、実際の列名変更などへ直す必要がある。
今回のPRでも、変更後のスキーマだけを見ると seat_category が存在する。新規予約のテストも成功する。しかし、既存の seat_type の値をどう移すかは、その二つだけでは確認できない。
DB変更では、最終形に加えて「今ある1行ずつに何が起きるか」を読む。 AIが生成しても、人が手書きしても、見る場所は変わらない。
1. 既存の行は、変更後にどんな値になるか
最初に見るのは、テーブル名や列名の見た目ではなく、既存データの行き先だ。
- 削除される列やテーブルはあるか
- 新しい必須列に、既存行の値をどう入れるか
- 型を変えた時、変換できない値はないか
- 一意制約を加えた時、重複する値はないか
- 外部キーや関連する画面、集計処理へ影響しないか
PostgreSQLの公式ドキュメントでは、DROP COLUMN を実行すると、その列にあったデータは見えなくなると説明されている。また、NOT NULL などの制約を加える時は、既存データが条件を満たすか確認される。開発DBに問題のある値がなければ通っても、本番には空欄や重複が残っているかもしれない。
そこで、適用前に件数で確認する。
SELECT COUNT(*) FROM reservations;
SELECT COUNT(*) FROM reservations WHERE seat_type IS NULL;
SELECT seat_type, COUNT(*)
FROM reservations
GROUP BY seat_type; この例で知りたいのは、SQLが実行できるかだけではない。「移す対象は1,186件」「空欄は12件」「想定外の席種は2種類」と、作業前の状態を説明できることだ。
本番データそのものを個人端末へ持ち出す必要はない。権限を絞った検証環境、個人情報を伏せた複製、集計結果など、案件のルールに合う方法を選ぶ。大事なのは、空のDBで通った結果を、本番の既存データでも安全だと言い換えないことだ。
2. 実行中に、利用者の操作をどれだけ止めるか
データが消えなくても、DB変更が長時間テーブルを使えなくすれば、予約や決済は止まる。
PostgreSQLのALTER TABLEは、操作によって必要なロックの強さや、テーブル全体を書き換えるかが違う。列の型変更では、通常、テーブルとインデックスの書き換えが起きる。既存行が多いほど時間がかかり、その間の読み書きへ影響する場合がある。
そのため、マージ前に少なくとも次を決める。
- どの環境で、どの件数を使って所要時間を測ったか
- 実行中も予約の作成や更新ができるか
- 何分を超えたら中止するか
- 誰がログと利用者画面を見るか
- 問題が起きた時、新しい書き込みをどう扱うか
「マイグレーションは数秒のはず」は計画ではない。件数、測定結果、中止条件まで書くと、急いでいる時も判断を共有できる。
Prismaは、本番向けのマイグレーションを個人端末から直接実行する方法を原則として勧めず、CI/CDの管理された手順で適用するよう案内している。誰の端末に本番接続先が残っているか分からない状態より、実行者、対象環境、ログを追える方が、異変の後に調べやすい。
3. 「戻す」は、コードとデータを分けて考える
今回、担当者の頭に最初に浮かんだのは「PRをrevertすれば戻る」だった。しかし、コードの変更履歴と、DBに保存されていた値は別物だ。
列を消した後に逆向きのマイグレーションで列を作り直しても、消える前の座席区分が自動で入るとは限らない。Prismaの公式ガイドも、逆向きのマイグレーションがDBの構造を戻せても、データ移行やアプリ側の変更までは戻さない点に注意を促している。
だから「ロールバックできます」ではなく、次を具体的にする。
- アプリのコードをどの版へ戻すか
- DBの構造を何の手順で戻すか
- 消えたり変換されたりしたデータを何から復元するか
- 復元後、件数と代表的な予約をどう確認するか
- 適用後に入った新規予約をどう守るか
バックアップがあるだけでも足りない。取得時刻、復元に必要な時間、復元手順を試した日まで分からなければ、29分後の開場に間に合うか判断できない。
「戻す」が曖昧なままなら、それは失敗時の手順ではなく願いに近い。適用前に、誰が何を見て戻ったと判断するかまで言葉にする。
古い列をすぐ消さず、追加・移行・切替・削除に分ける
座席区分の変更は、1回のリリースで古い列を消す必要がなかった。
安全性を優先するなら、例えば次のように分けられる。
seat_categoryを空欄を許す状態で追加する- アプリから新旧の列へ同じ値を書き込む
- 既存の
seat_typeをseat_categoryへ少しずつ移す - 件数と値の分布を照合し、読み取り先を新しい列へ切り替える
- 問題なく動く期間を置き、最後に古い列を削除する
これは、新しい仕組みを先に広げ、古い仕組みを後から閉じる「expand and contract」と呼ばれる進め方の一例だ。Prismaのデータ移行ガイドも、新しい列を追加し、データを移し、最後に古い列を削除する流れを示している。
一度に終わらせる方法よりリリース回数は増える。それでも、古い列が残っている間は比較や切り戻しがしやすい。新旧へ同時に書く期間には差分監視が必要で、二重書き込みを長く放置しない期限も要る。万能な手順ではないが、消してから祈るより、確認できる地点を増やせる。
AIへ依頼する時も、完成後のスキーマだけを頼まない。
既存のseat_typeをseat_categoryへ移行したい。
次を先に示してください。
- 既存行の値が各手順でどう変わるか
- 削除、型変更、必須制約、テーブル書き換えを伴う操作
- 本番に近いデータで確認するSQLと期待件数
- アプリとDBを戻す手順、戻せないデータ
古い列はこのPRでは削除しないでください。
実行前にマイグレーションSQLを提示し、適用は人の確認で止めてください。 AIは、変更案や確認SQLを速く出せる。ただし、本番に何件あり、何時まで止められ、どのバックアップを使えるかは、リポジトリのコードだけでは分からない。そこは作業を任せる側が、運用の事実をつないで判断する場所だ。
大きな差分を確認しやすく分ける方法は、AIで差分が膨らんだ。レビュー前に小さく切り直す勇気でも扱っている。テスト成功を完了の証拠にする時の見方は、AIが書いたテストは全部成功。それでもマージ前に利用者の動きを確かめる理由も参考になる。
「AIがやった」を隠す前に、今ある事実を共有する
16時38分、担当者は端末から手を離した。自分だけで復旧SQLを流す代わりに、劇場スタッフと同僚へ短く伝えた。
座席区分の列変更で、古い列を削除して空の新しい列を作る処理が実行されました。受付画面では1,186件の席種が空欄です。新しい書き込みは止めています。16時前のバックアップと予約CSVがあるため、復元できる件数と所要時間を2人で確認します。開場時刻の判断を16時45分に共有します。
書いている間も、「AIが作った変更を自分が見落とした」と先に謝りたくなった。でも、原因を短く断定するより、何が起きたか、今止めていること、残っている復旧材料、次に決める時刻を分けた。
同僚はバックアップの復元を担当し、担当者は予約CSVと照合した。劇場スタッフは、開場を15分遅らせる案内と、車いす席・招待席の紙の控えを準備した。すべてを一人で抱えた時より、確認する対象が見えた。
17時04分、1,186件のうち1,174件はバックアップから戻り、残る12件は16時以降の予約CSVから復元できた。席種別の件数と代表的な予約を確認し、17時15分に開場した。
翌週、チームはDB変更のPRに三つの欄を追加した。
- 既存行への影響
- 実行時間と利用者への影響
- コード、構造、データを戻す手順
AI生成かどうかを示す欄も残したが、それだけで合否は決めない。人が書いたSQLにも同じ確認をする。変えたのは、AIを使った人を責めることではなく、空の開発DBだけで安全を判断しない流れだった。
次のDB変更で、まず一つだけ行うなら、マイグレーションファイルを開き、削除される列とテーブルを探してほしい。その名前に本番の値が入っているなら、適用前に件数と復元元を確認する。
テストが通ったことと、今あるデータを失わずに変更できることは別の確認だ。 その違いを言葉にできれば、失敗を隠して一人で戻そうとする場面から、止めて、共有して、確かめる作業へ戻れる🙂
AIを使うDB変更の確認手順を整理する
ページへ移動参考にした情報
- Prisma Docs「Customizing migrations」(2026年8月30日確認)
- Prisma Docs「How to migrate data with Prisma ORM using the expand and contract pattern」(2026年8月30日確認)
- Prisma Docs「Deploy migrations from a local environment」(2026年8月30日確認)
- Prisma Docs「Generating down migrations」(2026年8月30日確認)
- PostgreSQL 18 Documentation「Modifying Tables」(2026年8月30日確認)
- PostgreSQL 18 Documentation「ALTER TABLE」(2026年8月30日確認)