HN 日本語サマリー

← 一覧へ戻る
プログラミング

なぜAIの1日のコストが1ヶ月分のサーバー費用を超えたのか?

Why did one day of AI cost more than a month of servers? (junueno.dev)

14 pointsby dxs11 コメント

要約

ある日、LLM APIのコストグラフに突如として富士山のようにそびえ立つピークが現れ、その1日だけで1ヶ月分のサーバー費用を上回るAI利用料が発生しました。この異常な出費の原因は、デプロイ順序の誤りと、タスクキューの自動リトライ機能、そして処理の非べき等性が組み合わさった「リトライストーム」でした。LLMへの呼び出し自体は成功しているにもかかわらず、データベースへの書き込み失敗により、同じ処理が21回も繰り返され、その都度課金が発生していたことが判明しました。

全文翻訳

なぜAIの1日のコストが1ヶ月分のサーバー費用を超えたのか? 2026-06-29 · llm, claudecode, idempotency, ai いつもの話です。私は、CFOが2日で本番環境にデプロイしたSaaSを運用しています。非エンジニアのエグゼクティブがClaude Codeを使って迅速に何かを構築し、エンジニア(私)がバックエンドを一つずつ見ていくという状況です。見るたびに何かが出てきます。今回は「シークレットがどこにあるか」でもなく、「テストが一つもない」でもありませんでした。今回は、お金が燃えました。 ある日、LLM APIのコストグラフを眺めていると、富士山のようにそびえ立つ1日のピークがありました。他の日はすべて底辺に張り付いているのに、その1日だけが空を突いています。だいたい1ヶ月分の請求額の半分がその1日に集中していました。正直に言うと、その数字を見たとき、胃が締め付けられる思いでした。なぜなら、その1日だけのAI利用料が、1ヶ月分のサーバー費用全体よりも高かったからです。サーバーフリート全体を1ヶ月運用するよりも、AIに1日話させる方が安いはずなのに、どうしてこんなことになるのでしょうか? そこで、それを作った人物(CFO)に尋ねました。「あの日に何をしたのですか?」 答えは「正直、何をしたか覚えていない」でした。 そんなはずはありません。しかし、これは非難の話ではありません(半分はそうではありません)。深く掘り下げるほど、私は「もちろん彼らは覚えていないだろう」という結論に至りました。お金を燃やしたのは人間ではなかったのです。それはリトライの仕組みでした。 ## 調査 ### 最初は彼らが一日中叩きまくったと仮定した 最初の私の読みは、おおよそこうでした。「あなたはあの日にたくさんの機能を構築し、本番環境で何度もテストし、そのたびに高価なLLMを叩いた。千回斬られて死ぬようなものだ。」 そしてそれはもっともらしく見えました。その日のコミット履歴は朝から晩までびっしりで、AI生成フローに関する20以上の変更がありました。そのため「人間による繰り返しの緩慢な燃焼」には根拠があるように見えました。 しかし、実際にアプリ側のログ(タスクキュー、DB、リクエスト)を掘り下げてみると、状況は全く異なっていました。それは緩慢な燃焼ではありませんでした。同じ重いバッチが、マシンによって、完全に、何度も何度も再実行されていたのです。1つのテナントに対して、通常1回実行されるジョブが21回実行されていました。人間が1日に同じボタンを21回押すことはありません。ボタンを押していたのは人間ではありませんでした。 ### 最も恐ろしかったのは「成功したのに、その後落ちた」という部分だ これがインシデント全体の核心なので、ゆっくり説明させてください。そのバッチはいくつかのLLMを順次呼び出し、結果をDBに保存していました。フローは概ね以下の通りです。 1. いくつかのLLMに大量のクエリを送信する(ここでお金がかかる) 2. 返された結果をDBに書き込む 問題はステップ2にありました。書き込みが、追加されるはずだったがまだ存在していなかったカラムを参照していたのです。DBにはそのカラムがなかったため、「column does not exist」というエラーが発生し、ジョブは500を返しました。 「失敗した」と聞くと、あなたは当然「呼び出しが失敗して、無駄になった」と想像するでしょう。違います。すべてのLLM呼び出しは成功していました。すべて200番台でした。つまり、そのすべてが適切に課金されていたのです。あなたは支払い、結果を受け取り、そして最後のステップである保存でつまずいたのです。 レストランの例えで言えば、フルコースを食べ終え、勘定を支払い、まさに「ごちそうさま」と言おうとした瞬間に、つまずいて倒れ、記憶を失ってしまったようなものです。気がつくと席に戻っていて、また同じフルコースを食べ始めている。それを21回繰り返すのです。あなたが食べたもの(=課金されたもの)は元に戻りませんが、毎回ゼロから始まるのです。 「リトライストーム」という言葉があります。通常、それは「呼び出しが失敗し、また失敗し、また失敗する」という、失敗の連続を想像するでしょう。しかし、これは失敗ではありませんでした。成功(ヒット)を毎回捨てて、新しいヒットを生成するというストームでした。それが直感に反する部分であり、最も恐ろしい部分です。 ## なぜこんなことが起きたのか?原因は2つあった マシンが21回繰り返したのは、2つの落とし穴が連携していたからです。 ### 落とし穴1:デプロイ順序が逆だった コードは新しいカラムが存在することを前提として本番環境にデプロイされましたが、そのカラムを追加するマイグレーションはまだ本番環境に適用されていませんでした。コードが先、スキーマが後。この順序では、コードは存在しないカラムにアクセスしようとして、決定的に失敗します。「決定的に」というのがミソです。これは、何度リトライしても決して自己修復しない種類の失敗です。 ### 落とし穴2:失敗すると、タスクキューが親切にも再実行する マネージドなタスクキューは、ジョブが500で停止すると「ああ、失敗した。もう一度実行してあげよう」と自動的に再実行します。一時的なネットワークの不具合の場合、これは正しい親切心です。しかし、この失敗は「カラムが存在しない」というものでした。何度再実行してもカラムは増えません。それは、親切心から、修正不可能な失敗を無限に繰り返していました。そして、そのバッチはべき等性を持っていなかった(既に処理された作業をスキップしなかった)ため、再実行のたびに最初からやり直されていました。そのため、毎回フルLLMの請求が発生したのです。 決定的な失敗 × 自動リトライ × 非べき等性。この3つが絡み合うと、静かにお金が燃えます。その人が覚えていないのも不思議ではありません。彼らは何もしていません。ボタンを押し続けていたのはキューでした。 私が説明すると、CFOは顔をしかめて「うーん?」と言いました。(非エンジニアにとって「成功したのに、課金されて、その成功を捨てた」というのは、本当に受け入れがたいことです。) ## 私が学んだこと:リトライは常に親切ではない 自分自身のために教訓を書き留めておきます。なぜなら、同じ立場にある人(他人の動いているプロダクションを引き継いだ人)には必ず役立つからです。 * **決定的な失敗は、リトライしても改善しない。** スキーマの不一致、4xxクラスの「あなたが間違っている」というエラーなど、何度投げても結果は同じです。これらは即座に「中止」として扱い、常にリトライの上限を設定してください。 * **リトライは万能の保険ではない。** 副作用が大きいほど、べき等性が必要になります。コストのかかる作業(課金API、LLM呼び出し)を実行するバッチは、初日から「既に完了した作業をスキップする」機能が必要です。これがなければ、再実行は「やり直し」ではなく「二重課金」になります。 * **デプロイは「スキーマ、それからコード」の順序で行う。** まずDBの変更を適用し、それからそれを使用するコードをデプロイします。逆にすると、そのギャップで決定的なエラーを大量生産します。 * **コストが観測可能でなければ、「燃え尽きてから」しか気づかない。** 今回たまたまコストグラフを見たからこそ、これに気づけたのです。スモークディテクター(本番環境とテスト環境で別々のキー、予算アラートなど)がなければ、請求書が届くまで誰も気づきません。 Vibe codingは確かに非エンジニアがプロダクションを構築する敷居を下げました。しかし、「どのように壊れるか」そして「どのように高価になるか」を見ることは、まだ別のスキルです。その部分は、それを受け継ぐエンジニアの仕事です。 機能は2日で構築できます。「失敗を優雅にリトライする」が「成功を捨てて二重課金する」に変質する瞬間を防ぐのは、2日ではできません。リトライは常に親切ではありません。サーバー費用よりもAIの請求書が大きくなるのを二度と見たくありません。だから、自分への警告としてここに残しておきます。