メインコンテンツへスキップ
見出し画像
Photo bynovel_mint2747

タイムアウト対策で下げた精度が、無料枠20回/日という思わぬ真実を暴いた話 — Gemini API個人開発の失敗録

    出力を強化したら30秒の壁にぶつかり、精度を下げたら今度は無料枠の勘違いが発覚しました。「調子よく動いている」と「正しく理解している」は別物だと痛感した2日間の記録です。

    ■ 何が起きたか

    個人開発中の、おつまみ提案アプリの話です。知り合いの居酒屋の店長や家族にも使ってもらうつもりで作っていて、Lambda + API Gateway(HTTP API) + Gemini APIという構成で、ユーザーが条件を入力すると、おつまみとその作り方をGeminiに生成させて返す/suggestというエンドポイントを持っています。

    身内向けとはいえ課金は避けたかったので、Gemini APIの無料枠を使い切って請求が発生することがないよう、アプリ側でも1日あたりのリクエスト数を自前でカウントし、上限に達したら「本日の上限に達しました、明日リセットされます」という専用メッセージを返す仕組みを最初から入れていました。当時把握していたGemini側の無料枠が15RPM/1500RPDだったため、余裕を見て内部の上限は1000回に設定していました。

    この状態で、Geminiの出力に「調理手順(火加減・時間・見た目つき)」「分量」「合うお酒」まで含む詳細なJSON構造を返させるよう強化したところ、体感できるレベルで遅くなりました。

    ■ 第一の壁:Lambdaのタイムアウト

    ●症状

    /suggestが、まれにタイムアウトし、502/504相当のエラーが返るようになりました。

    ●原因

    出力構造を強化した影響で、Gemini(gemini-3.6-flash)の生成時間が26〜30秒台まで伸び、Lambda自体がタイムアウトするようになっていました。

    Lambdaの実行時間は最大15分まで延長できますが、今回は延ばさない方針にしました。理由は2つあります。ひとつは、そもそも30秒近く待たせること自体がユーザビリティとして良くないこと。もうひとつは、シンプルなLambda1本で完結させたい構成で、非同期化などで複雑にしたくなかったことです。

    加えて、手前にいるAPI Gateway(HTTP API)側の統合タイムアウトは30秒固定で、REST APIと違って引き上げ申請もできません(2026年8月時点、AWS公式ドキュメントで確認)。つまりLambda側だけ延ばしても、API Gateway側で30秒に達した時点でユーザーにはエラーが返ってしまいます。タイムアウト値をいじるのではなく、生成ロジック側で根本的に時間を短縮する方針にしました。

    ●対応1:thinking_levelを下げる

    thinking_config=ThinkingConfig(thinking_level=MINIMAL)

    これをGenerateContentConfigに設定し、生成時間を6〜12秒台まで短縮しました。

    ●副作用:指示遵守の精度が落ちた

    ところがthinking_levelを下げた結果、本番で以下の副作用が発生しました。

    ・「合うお酒」の項目に、候補リストにない銘柄名(「△△焼酎(shochu)」のように、候補リストの表記をそのままコピーしたような値)を返し、銘柄マスタとの照合に失敗してパースエラーになるケース
    ・外側のオブジェクト(suggestions配列を包むもの)を省略し、配列だけを直接返してくるケース

    スピードは出たものの、「指示通りの形式で返す」という基本的な部分が崩れてしまったかたちです。

    ●対応2:response_schemaで構造を強制する

    response_schema(銘柄名にあたるフィールドは候補リストのenumで制約)を追加し、JSON構造と銘柄名の妥当性をAPIのトークン生成レベルで強制しました。thinking_levelを下げたままでも、構造と銘柄名の破綻を防げる形です。

    ●検証

    本番エンドポイントに連続でリクエストし、6〜12秒で正常応答すること、銘柄名が候補リスト内に正しく解決されることを確認しました(1回だけGemini側の一時的な503 UNAVAILABLEが発生しましたが、これは今回の不具合とは別の話です)。

    ここで「タイムアウト対応、完了」と思っていました。

    ■ 第二の壁:「予期しないエラーが発生しました」の正体

    ●症状

    検証を続けていたところ、今度は/suggestが汎用的なエラーメッセージ「予期しないエラーが発生しました。」(500)を頻発するようになりました。

    ●原因調査

    CloudWatch Logsで実際のGemini側のレスポンスを確認すると、アプリ側で用意していたレート制限のエラー(RateLimitExceededError、GeminiDailyLimitExceededErrorといった独自の例外)ではなく、Gemini API自体の無料枠超過(429 RESOURCE_EXHAUSTED)が原因でした。


    quotaId: GenerateRequestsPerDayPerProjectPerModel-FreeTier
    quotaValue: '20'

    ●発覚した勘違い

    Google AI Studioのレート制限ダッシュボードで実測したところ、当時使っていたモデル(無印のFlash系)の無料枠は、RPM 5・RPD 20・TPM 250Kしかありませんでした。一方、READMEや設計ドキュメントに書いていた「15RPM/1500RPD」という前提は、おそらく別モデル・別条件のときの情報が古いまま残っていたもので、実態と合っていませんでした。

    さらに、この429エラーはAPIハンドラー側で個別ハンドリングしていなかったため、汎用のexcept Exceptionに落ちて、原因が一目ではわからないメッセージになっていました。

    ここで気づいたのは、レート制限の数値だけの話ではありませんでした。「本日の上限に達しました」という自前の安全装置は、内部カウントが1000に達して初めて働く設計です。ところが実際の無料枠はRPD 20しかなかったので、内部カウントが1000に届くよりずっと前に、Gemini側の生の429エラーで落ちてしまいます。つまり、身内のユーザーを守るために入れておいたはずの仕組みが、前提の数値を間違えていたせいで一度も機能せずに素通りされていた、ということです。

    同じダッシュボードで比較すると、Flash-Lite系のモデルはRPDが500(無印Flashの25倍)で、TPMは同じ250Kのままでした。API経由で実在するモデル IDを確認したうえで、Flash-Liteモデル(gemini-3.1-flash-lite)に切り替えることにしました。

    ●対応

    1. Geminiを呼び出す部分のモデル指定を、無印Flash系からFlash-Lite系(gemini-3.1-flash-lite)に変更(RPD 20→500、RPM 5→15)

    2. 自前の安全装置(内部カウントの上限)を1000→450に修正。実測のRPD 500に対して安全マージンを残しつつ、初めて意味のある値にした

    3. READMEや設計ドキュメントの無料枠の記載を、実測値(15RPM/500RPD、Flash-Liteモデル)に訂正

    ●検証

    ローカルから実際のAPIキーで複数回呼び出し、3.8〜4.0秒で安定して正常応答することを確認しました(thinking_level=MINIMALとresponse_schemaは継続利用、構造・銘柄名とも問題なし)。


    画像

    ■ 残っている宿題

    ・429 RESOURCE_EXHAUSTED(無料枠超過)は、今も汎用エラーメッセージに落ちている。個別ハンドリングして、ユーザーにもう少し分かりやすく伝えられる余地がある
    ・無料枠の数値はGoogle側の都合で変わりうる。ドキュメントに書いた数値を信じきらず、定期的にダッシュボードで実測値を確認する運用にしたい
    ・出力構造をさらに拡張する場合、HTTP APIの30秒という上限は変わらないので、生成時間の再測定が必要


    ■ 学んだこと

    ☐ 「延長できる制約」と「延長できない制約」を分けて考える。Lambdaのタイムアウトは延ばせても、API Gateway(HTTP API)側は30秒固定。延ばせるからといって、それが最適な解決策とは限らない
    ☐ thinking_levelのようにモデルの「賢さ」を落とす変更をするときは、response_schemaなど出力の形式を強制する仕組みとセットにする
    ☐ 汎用のexcept Exceptionは、原因究明を遅らせる。外部APIのエラーは種類ごとに拾っておく
    ☐ READMEに書いた数値は「書いた時点の事実」でしかない。無料枠のような変動する値は、公式のダッシュボードで定期的に裏取りする
    ☐ 「速くなった」「エラーが減った」という体感だけで安心せず、ログで実際に何が起きているかを確認する
    ☐ ユーザーを守るための安全装置(上限チェックなど)は、入れて終わりにせず、前提にした数値が正しいかまで検証する。数値が間違っていれば、安全装置は「入っているのに機能していない」状態になる

    タイムアウト対策のために精度を落とした結果、パースエラーという副作用に気づき、その検証を続けていたら今度は無料枠の勘違いに気づく。振り返ると、最初のタイムアウトが起きていなければ、無料枠の誤りにはもっと後になってから、もっと大きな形で気づいていたかもしれません。ひとつの不具合が、隠れていた別の思い込みをあぶり出してくれることもあるようです。

    ■ 参考リンク

    ▶ https://ai.google.dev/gemini-api/docs/rate-limits

    ▶ https://ai.google.dev/gemini-api/docs/thinking

    ▶ https://ai.google.dev/gemini-api/docs/structured-output

    ▶ https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-quotas.html


     
     

    koppe

     
     
    cloud×生成AIに興味があるエンジニアです。 適当にゆる〜く投稿してます

    あなたへのおすすめ