なぜ1日のAIコストがサーバー1ヶ月分を超えたのか?
非エンジニアがClaude CodeでAI機能を素早く構築したところ、データベースのカラム欠落による決定論的失敗が自動リトライを引き起こし、同じタスクが21回も実行され、1日のAIコストがサーバー1ヶ月分を超えた。その原因と教訓を解説する。
非エンジニアのCFOがClaude Codeを使ってわずか2日でAI機能を本番投入しました。その機能を引き継いだエンジニアである私は、バックエンドを一つずつ確認していく中で、驚くべきコストの異常に気づきました。ある1日だけ、LLM APIのコストが突出して高く、その月の請求額の約半分を占めていたのです。サーバー群全体の1ヶ月分の運用費よりも、たった1日のAI利用料のほうが高かった。
最初は、その日のコミット履歴が密集していたため、人間が繰り返しテストしたのだと考えました。しかし、アプリケーションログを詳しく調べると、全く異なる実態が浮かび上がりました。同じバッチ処理が、機械によって21回も実行されていたのです。あるテナント向けのジョブが、本来1回で済むところを、21回も繰り返されていました。
問題の核心は、タスクの流れにありました。複数のLLMにクエリを送信し、結果をデータベースに保存するという処理です。LLMへの呼び出しはすべて成功し、課金も発生しました。ところが、保存先のテーブルに必要なカラムがまだ存在しておらず、データベースが「カラムが存在しません」というエラーを返し、ジョブは500エラーで終了しました。つまり、すべてのLLM呼び出しは成功し、課金されたにもかかわらず、最後の保存ステップで転んでしまい、結果は捨てられ、再実行されていたのです。
この「リトライストーム」は、2つの落とし穴が重なって発生しました。1つ目は、デプロイ順序の逆転です。コードが先に本番環境にデプロイされ、そのコードが依存するデータベースのマイグレーションが後回しにされました。2つ目は、タスクキューが500エラーを自動リトライの対象とみなしたことです。しかも、このバッチ処理は冪等ではなかったため、再実行のたびに全額のLLMコストが再発生しました。
CFOに事情を説明すると、彼は「成功したのに課金されて、その結果が捨てられるのか?」と困惑していました。非エンジニアには確かに理解しがたい現象です。しかし、この事例から得られる教訓は明確です。決定論的失敗(スキーマ不一致や4xx系エラー)はリトライしても解決しないため、即座に中断すべきであり、コストが発生する処理には必ず冪等性を持たせる必要があります。デプロイは「スキーマ→コード」の順序を守り、コスト監視の仕組み(専用キー、予算アラート)を導入すべきです。
「バイブコーディング」は確かに非エンジニアの生産性を向上させましたが、システムがどのように壊れ、コストが増大するかを理解するスキルは別物です。機能は2日で作れても、「優雅なリトライ」が「成功を捨てて二重課金する」という悪夢に変わるのを防ぐのは、もっと時間のかかる仕事です。リトライは常に親切とは限らない——この経験は、私にとって最も高価な教訓となりました。