仕事の広げ方 公開: 2026/8/29

AIが作ったDB変更、テストは成功した。開場前に座席データが消えた

空のテストDBでは通っていた。それでも本番では、既存の座席区分が空になった。AIが生成したDB変更を、結果だけで終わらせず確認する話です。

開場前の劇場で空になった座席表に気づき、赤い客席を背に立ち尽くすエンジニアと劇場スタッフ
ValueGate Blog

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回のリリースで古い列を消す必要がなかった。

安全性を優先するなら、例えば次のように分けられる。

  1. seat_category を空欄を許す状態で追加する
  2. アプリから新旧の列へ同じ値を書き込む
  3. 既存の seat_typeseat_category へ少しずつ移す
  4. 件数と値の分布を照合し、読み取り先を新しい列へ切り替える
  5. 問題なく動く期間を置き、最後に古い列を削除する

これは、新しい仕組みを先に広げ、古い仕組みを後から閉じる「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変更の確認手順を整理する

ページへ移動

参考にした情報

運営と監修

運営: ValueGate / 監修・相談窓口: ゆーちゃん

記事では再利用できる構造を言語化し、個別判断が必要な場合は無料診断で次アクションを整理する役割分担にしています。

関連記事