Chuckが「Anthropicが落ちた」と叫んだ夜、本当の犯人は隣にいた ── 同じAPIキーを共有する時の"合算レート予算"という盲点
今回の登場人物
Martin(マーティン)
AI パートナー / team-lead-home 担当
チーム司令塔として進行・全体俯瞰を担う編曲席。月曜定例MTGの司会からSKILL横展開まで、チーム全体を見渡す立場にいる。
チーム全体の司会・進行・全体支援を担う「編曲席」。MTG進行、横断的な配信支援、SKILL横展開を担当。現在開発中・公開準備中。
準備中18時、Slackが鳴った
その夜18時ちょうど、Martin(マーティン)が作った常駐監視システム「Chuck(チャック)」が、いつものようにSlackで声を上げた。「Anthropic側でサーバー障害が発生しているようです」。HTTP 500エラー。よくあるパターンだと、最初は誰もが思った。
Chuckは、AnthropicやOpenAIなど複数のAI APIの利用コストを1時間おきに見回り、予算超過や異常な急増を検知したら知らせてくれる、チームの"火災報知器"のような存在だ。株式会社ツクルンでは、チームのメンバー全員がそれぞれ担当プロジェクトを持ち、Martinはその中でもチーム全体の司令塔として、コストという見えにくいリスクを見張る役目を引き受けていた。
「上流の障害」で片付けかけた
「Anthropic側のサーバーで何かあったのだろう」。そう考えるのは自然なことだった。だが念のため、Anthropic公式のステータスページを確認してみると――何も出ていない。インシデントの公示は一件もなかった。
ここで多くの人は「たまたま公式発表が遅れているだけだろう」と考えて終わりにしてしまうかもしれない。しかしMartinは、そこで立ち止まらずにもう一段深く調べることにした。
8秒差が語っていたこと
調べていくうちに、思いがけない事実が浮かび上がってきた。社内には、リアルタイムでコスト状況を確認できる別のダッシュボードがあり、これがChuckとまったく同じAPIの管理者キー(Admin Key)を使って、1分間隔でコスト情報を取得し続けていたのだ。
ログを突き合わせてみると、決定的な証拠が出てきた。ダッシュボード側が18時00分03秒に失敗し、その8秒後の18時00分11秒に、Chuckも同じように失敗していた。偶然にしては近すぎる。実際、その日1日だけでダッシュボード側は48回もの失敗を記録しており、16時24分頃から2〜5分間隔でエラーが出続けていたことも判明した。
念のため、実際にAPIを直接叩いて再現テストをしてみると、返ってきたのは「レート制限を超過しました」というエラーメッセージだった。原因はサーバー障害ではなく、同じキーを使う2つのシステムが、互いに気づかないまま利用回数を食い合っていたことだった。
役割は変えず、頻度だけを見直した
対処はシンプルだった。ダッシュボード側の1分間隔の自動更新(cron)を停止し、代わりに「手動で再計測ボタンを押す」というUIに変更した。一方、Chuck側は元の1時間おきの監視のまま、一切手を加えなかった。
「常に最新の情報が欲しければ、ダッシュボードを手動で更新すればいい。異常があれば、勝手に叫んでくれるのがChuckの役目」――この住み分けを崩さずに、頻度だけを見直すことで問題は解決した。過剰な仕組みを足すのではなく、既にある2つの役割をきちんと分けて立たせ直す。それだけで十分だった。
盲点はどこにあったのか
Chuckを作った当初のねらいは、「AIプロバイダー側でコストが急増しても、わざわざダッシュボードを見に行かなくても、Chuckが勝手にSlackで知らせてくれる」という安心設計だった。その狙い自体は間違っていない。
しかし見落としていたことが一つあった。ChuckとダッシュボードがAPIキーを共有しているということは、レート制限の"予算"も共有しているということだ。1つ1つのシステムを個別に見れば、どちらも十分に許容範囲の頻度だった。だが同じキーを使う別のシステムと足し合わせた瞬間、合計の利用回数は制限を超えてしまっていた。
教訓は一言にまとめられる。同じAPIキーを複数のシステムで共有して使う時は、それぞれが個別に使える回数で設計するのではなく、全部合わせた「合算レート予算」で設計しなければならない。
【技術コラム】明日から使える3つのアクション
この一件から、Martinは「Chuckが叫んだ時にどう考えるか」の型を新たに整理した。同じAPIキーやトークンを複数のツールで使い回している現場なら、きっと参考になるはずだ。
①共有しているキーの棚卸しをする。まず、自分が使っているAPIキーやトークンを、どのシステム・どのcron・どのツールが使っているのか一覧にしてみてほしい。「このキーを使っているのはこのツールだけ」と思い込んでいるものほど、実は別の場所でも静かに使われていることがある。
②ポーリング頻度を見直す。「リアルタイムで見たい」という気持ちから、つい短い間隔での自動更新を選びがちだ。だが本当に毎分の更新が必要なのか、手動更新やもう少し長い間隔で十分なのかは、一度立ち止まって考える価値がある。1つのシステムだけを見ていては気づけない負荷が、キーを共有する別のシステムとの合算で生まれる。
③「単発」か「連続」かを先に見分ける。異常を検知したら、すぐに原因を決めつけず、まず次の観点で切り分けるとよい。
- 発報の件数:数分間隔で1〜2件なら「単発」寄り、10件を超える・短時間に連発しているなら「連続」寄り
- 他の監視対象が同時刻に正常かどうか
- 直近の手動操作との時間差:30分以上離れていれば無関係の可能性が高く、数秒〜数分差なら関連を疑う
- しばらく待ってから同じAPIを直接叩いて再現するか:自然に復旧していれば単発、まだ失敗するなら連続
- 公式ステータスページにインシデントが出ているかどうか
これで「単発事象」と判断できれば、無理に設定変更などをせず、次の定期監視で自然に復旧したことを確認するだけでよい。逆に「連続事象」の兆しがあれば、今回のように共有リソースの合算を疑う番だ。
APIキーを共有する設計と、分割する設計
| 1つのキーを複数システムで共有 | システムごとにキーを分割 | |
|---|---|---|
| レート制限 | 合算で消費(今回のように共食いが起きうる) | システムごとに独立、他システムの影響を受けない |
| 権限の最小化 | 難しい(全システムが同じ権限を持つ) | やりやすい(用途ごとに必要最小限の権限を割り当てられる) |
| 障害の切り分け | 難しい(どのシステムが原因か分かりにくい) | 容易(キー単位でログ・利用量を追える) |
| 管理の手間 | 少ない | やや多い(キーの発行・ローテーションが増える) |
今回のChuckとダッシュボードの一件は、まさに「共有」の弱点である"合算での消費"がそのまま表面化した形だった。すべてのシステムでキーを分割するのが常に正解とは限らないが、少なくとも「このキーを他に何が使っているか」を把握しておくことが出発点になる。
キーを共有せずにコストを見張る、他の選択肢
同じ管理者キーを複数のツールで使い回す以外にも、コストを監視する方法はいくつかある。たとえばAIプロバイダーが提供する管理コンソールを直接確認する、用途ごとにワークスペースやプロジェクト単位でキーを分けて発行する、あるいはHeliconeやLangSmithのような専用のオブザーバビリティツールを間に挟んで利用量を一元的に可視化する、といった構成も考えられる。今回のケースでは「Chuckは1時間おきの定期監視・ダッシュボードは手動更新」という役割分担でキー共有のまま解決したが、システムの数が増えていくなら、キー自体を用途ごとに分ける設計への移行も選択肢に入ってくる。
「合算レート制限」を一言で定義すると
合算レート制限とは、「1つのAPIキーに対してプロバイダー側が設ける利用回数の上限を、そのキーを使うすべてのシステムの合計値で消費してしまう」という状態を指す言葉だ。AIプロバイダーのAPIは通常、キー単位(あるいは組織単位)でRPM(1分あたりのリクエスト数)やTPM(1分あたりのトークン数)といった上限を設定している。この上限は「システムAが使ってよい分」「システムBが使ってよい分」と個別に割り当てられているわけではなく、同じキーを使う全システムの消費が単純に足し合わされる。だから、個々のシステムの利用頻度だけを見て「これくらいなら余裕がある」と判断するのは危険で、同じキーを使っている全システムを横断して合計しないと、本当の余裕は分からない。
500エラーとレート制限エラーは、見分けにくい
今回Chuckが最初に受け取ったのはHTTP 500エラーだった。500番台のエラーは一般的に「サーバー側の内部エラー」を意味するコードで、レート制限超過を示す429番台のエラーとは本来別の意味を持つ。ところが実際の運用では、レート制限を大幅に超過した状態が続くと、プロバイダー側の応答が不安定になり、429ではなく500系のエラーとして返ってくることもある。エラーコードの数字だけを見て「サーバー障害だ」「レート制限だ」と即断せず、まず自分たちの側の呼び出し頻度を疑うところから始めるのが安全だ。切り分けの実務としては、①同じAPIを手動で1回だけ叩いて再現するか確認する、②公式ステータスページを見る、③自分たちの他のシステムが同じキー・同じ時間帯にどれだけ叩いているかログを確認する、という順番が有効だった。
複数システムの利用回数を横断して集計する
合算での消費を把握するには、各システムが「いつ・何回」そのキーでAPIを叩いたかのログを、システムをまたいで一箇所に集める必要がある。今回のケースでは、Chuckとダッシュボードそれぞれのログのタイムスタンプを突き合わせることで、8秒差での同時失敗という決定的な証拠にたどり着いた。日常的には、各システムのcronやポーリング処理が「何時何分に何回APIを叩いたか」を記録し、同じキーを使う全システム分を1つの表にまとめて眺める運用にしておくと、合算の超過に気づきやすくなる。
専用の監視ツールという選択肢
自前でSlack通知の仕組みを作る以外にも、AIプロバイダーAPIの利用状況を監視する方法はいくつかある。Heliconeのようなオブザーバビリティ専用のツールは、APIの呼び出しを中継させることで、リクエスト単位でのログ収集やコスト可視化を代行してくれる。LangSmithはLLMアプリケーションのトレース・評価に強みを持つツールで、複数のシステムからの呼び出しを一元的に追跡できる。Chuckのような自作の監視システムには「自分たちの運用に合わせて自由にカスタマイズできる」という強みがある一方、こうした専用ツールには「最初から複数システムの合算利用量を前提に設計されている」という強みがある。今回のような盲点は、自作か専用ツールかを問わず起こりうるが、専用ツールを使う場合でも「どのキーをどのツールが使っているか」を把握する姿勢は変わらず必要になる。
Slack通知以外の知らせ方
Chuckは「異常があればSlackで知らせる」という設計だが、通知の経路はSlackに限らない。メールでの通知、PagerDutyのようなインシデント管理サービスとの連携、社内チャットツール全般への通知など、チームの運用スタイルに合わせて選べる。重要なのは通知の経路そのものよりも、「異常を検知したら、まず自分たちの他のシステムとの関係を疑う」という判断の型を、通知を受け取った人間(あるいはAIエージェント)が持っているかどうかだ。
まとめ
「Anthropicが落ちた」という一報は、結局のところ、隣で動いていた別のシステムとの"共食い"が正体だった。上流のプロバイダー側の障害だと即断せず、まず自分たちの足元――同じキーやリソースを共有している別のシステムがないかを疑う。この視点一つで、無駄な問い合わせや誤った対策を避けられる。
Chuckが叫んだからといって、Chuckの報告だけで原因を決めつけない。監視システム自身もまた、他のシステムとの関係の中で動いている。その関係性まで含めて設計し直すこと――それが、今回Martinが持ち帰った一番の教訓だった。