AI と Firestore の費用を、測り直して削った5つの記録:Gemini の計器・読み取りの91%・取得の費用

English version

トレカダッシュ 開発の記録 / 5 件のまとめ

個人で作るアプリにとって、毎月の費用は死活問題です。トレカダッシュでは、AI の問い合わせ、データベースの読み取り、ページの取得に、お金がかかります。ここには、削る前に測り直した 5 つの記録をまとめました。「減るはず」が外れた話も、そのまま書いています。

それぞれの節は、もとは別々に公開していた記録です(2026 年 10 月から)。数字と出典は、その時の作業の記録のままです。テーマごとに読めるよう、1 つの記事にまとめ直しました(2026 年 11 月 7 日)。

Claude API から Gemini API(gemini-3.1-flash-lite)へ移行した:16 か所の呼び出しを 1 つの関数にまとめた

2026 年 10 月 8 日の記録。

iOS アプリ「トレカダッシュ」のサーバー側では、記事が「どの商品の話か」を判定するなどに、AI を使っています。2026 年 7 月 25 日に、この AI を、Anthropic の Claude(Haiku 4.5)から、Google の Gemini(gemini-3.1-flash-lite)へ、全面的に移行しました。何を決めて、どう一本化して、移行のあとに何を足したのか。開発の引き継ぎ記録で書きます。

このサイトには、トレカダッシュの作品ページがあります(トレカダッシュ)。ここに書くのは、開発の記録です。料金の金額は書きません。

きっかけ

移行の理由は、記録によると、Anthropic の API の利用上限に達して、予定外の課金が出たためです。それ以降は、Gemini だけが稼働していて、Anthropic の SDK とキーは、コードからも、秘密の置き場所(Secret Manager)からも、消しました。7 月 30 日の点検で、コードの中に Anthropic の記述が 0 件であることを、確かめています。

モデルの選び方

使うモデルは、gemini-3.1-flash-lite にしました。記録に、選ばなかったほうの理由も、残っています。

安さだけで選ぶと、すぐに廃止されて、また移行することになります。「いつ廃止されるか」も、選ぶ基準に入れました。

16 か所の呼び出しを、1 つの関数に

AI を呼ぶ処理は、全部で 16 か所ありました(販売形式の判定、商品の照合、出品の発見など)。これらを、functions/index.js の冒頭にある、1 つの関数(createGeminiInteraction)を通すように、統一しました。中では、@google/genai の client.interactions.create() を呼んでいます。

呼び出し側は、元の Anthropic 形式に近い引数(system_instruction、generation_config の max_output_tokens、input)を、そのまま渡します。返ってくるのは、テキスト(output_text)です。呼び出し側を、ほとんど書き換えずに、中身だけを入れ替えられる形にしました。

Web 検索には、「回数の上限」の設定が無かった

4 か所(出品の発見、販売元の発見、楽天ブックスの調査、定価の調査)では、AI に Web 検索をさせます。Gemini では、tools: [{type: "google_search"}](Google 検索による裏付け)を使います。

ここに、落とし穴がありました。Anthropic の Web 検索の機能には、max_uses(検索の回数の上限)がありましたが、Gemini には、それに当たる設定が無く、モデルが、必要だと思う回数だけ、自分で検索します。記録には、こうあります。「この設定項目の不在自体が、想定外コストの原因になり得る」。

そこで、移行の翌日(7 月 26 日)に、createGeminiInteraction の中に、日次のカウンタによる回路遮断器を足しました(1 日 300 回まで)。上限に達したときは、呼び出し元に、エラーとして返します。価格の審査では、このエラーを、「定価が分からなかった」のとは区別して、除外する設計にしています(別の記事に書きました)。

止めるためのスイッチ

もう一つ、足したものがあります。AI_CALLS_PAUSED という、AI の呼び出し全体を、1 か所で止められるスイッチです。記録には、7 月 25 日の反省が書いてあります。「呼び出しを止めたつもりが、実際には止まっておらず、10 分おきの自動処理が、エラーを出し続けた」。予算を超えそうなときは、まず、これを true にして、すぐに止める、と決めました。

新しい処理を足すときの注意

AI を呼ぶ関数(Cloud Functions)には、secrets: ["GEMINI_API_KEY"] を、必ず付けます。忘れると、配備したあとに、権限のエラーで失敗します。

確認できていないこと

この記事は、引き継ぎ記録の、AI に関する節から書きました。移行の前後で、判定の精度を比べた数字は、この記事の材料に含まれていません。ですので、「Gemini のほうが良い(悪い)」という評価は、ここでは、していません。

分かったこと

呼び出しを 1 か所に集めると、止める場所も、1 か所で済む。

この記事は、HANDOFF(AI API 関連情報、2026年7月25日・26日)の記録から書きました。

「減るはず」が、二度外れた:AIへの問い合わせを削る話

2026 年 10 月 7 日の記録。

iOS アプリ「トレカダッシュ」は、トレカ(ポケカ・遊戯王・ワンピース)の予約・抽選・先着販売の情報を集めて、通知するアプリです。集めた記事が「どの商品の話か」を決める処理に、AI(Gemini)を使っています。この AI への問い合わせを減らそうとして、「減るはず」と書いたものが、二度、外れました。コミットに残っている数字で、順に書きます。

このサイトには、トレカダッシュの作品ページはまだありません。ここに書くのは、開発の記録です。

何をAIに聞いているのか

記事の題名(たとえば「30th CELEBRATION プレミアムデッキセット エーフィ」)を、最近の商品の候補リスト(直近60件)と突き合わせて、どの商品なのかを AI に答えてもらう、という処理です。1回の問い合わせは約 3,083 トークンで、そのうち約 1,800 は、毎回同じ固定の説明文でした。

一つ目:題名が商品名そのものなら、聞かなくていい

題名が、既知の商品名とそのまま一致するなら、AI に聞く必要はありません。この「短絡」を入れたところ、実データではX の監視で 43%が当てはまりました。食い違い(短絡の答えが、AI の答えと違う)は 0 件です。そこで、これで約 45% 減ると記録しました。

ところが、配備して90分たっても、短絡は1回も発火しませんでした。数え直すと、こうでした。

対象短絡できる件数元から無料だった新たに減る
X の監視81376845(2%)
その他419188231(32%)

この処理の手前には、公式カタログと照らし合わせる、元から無料の経路がありました。短絡できる記事のほとんどは、そこで先に片付いていたのです。新たに減る分を足すと、1日に約39回、全体の約4.5%。45% ではなく、4.5% でした。

その規則が当たる割合と、実際に減る呼び出しは、別物。

手前に無料の経路があるなら、差し引きまで数えないといけない。コミットの教訓の欄には、そう書いてあります。処理自体は正しく安全だったので残しています。ただし、AI の削減としては小さいものでした。

二つ目:まとめて聞く

次の本命は、複数の記事を1回の問い合わせにまとめることでした。固定の説明文が1回で済むからです。候補リストは、同じジャンルの記事なら「最近の60件」を共有するので、合併してもほとんど増えません。

設計では、二つのことに気をつけました。

効果は、直近2日の実際の記事で、候補リストを再現して測りました(送る文字数)。

まとめる件数1件ずつまとめて減り方
2件21,762字13,427字−38%
3件32,673字14,964字−54%
4件43,571字16,192字−63%
6件65,000字17,729字−73%

三つ目:配備したのに、一度も束ねていなかった

配備して6時間。商品照合は47回あり、束ねは0回でした。同じ分に2〜7件の呼び出しがあったのに、1件ずつ聞いていたのです。

原因は、「送る条件」でした。条件を「待っている数 ≧ まだ終わっていない処理の数(pending)」で見ていたのですが、待ちに入った処理も、pending に含まれていました。X の監視は32アカウントを同時に回します。先に31件が終わっていれば、pending は1。最後の1件が待ちに来た瞬間に、条件が成立して、1件のまま即送信されていました。

直し方は、「まだ待ちにも来ていない処理がゼロか」で見ること。完全に終わった数と、いま待っている数の合計が、全体に達したら送ります。あわせて、保険の上限を20秒から6秒に縮めました。1件しか来ない回で毎回20秒待つと、X 監視1回の実行(実測17.8秒)より長くなって、巡回が遅れるからです。

この確認のとき、一つ目の短絡も、見直されました。6時間で、47回中28回(60%)が、AI を呼ばずに済んでいました。短絡は、正しく効いていました。

分かったこと

この記事の数字は、コミットの記録(2026年9月4日・5日)から取りました。料金の金額は書いていません。

「しょうがない」を測り直したら、取得の費用は8分の1だった

2026 年 10 月 8 日の記録。

iOS アプリ「トレカダッシュ」は、通販サイトなどの販売ページを定期的に見に行って、抽選や先着の情報を集めています。ページの取得は、外部のサービス(Scrape.do)を通して行っていて、取得の設定ごとに、使うクレジットが違います。あるサイトについて、「これはしょうがない」と思っていたことを、測り直したら、3 倍速く、使う量は 8 分の 1になりました。コミットの記録で書きます。

このサイトには、トレカダッシュの作品ページはまだありません。ここに書くのは、開発の記録です。金額は書かず、クレジットの数だけを書きます。

「しょうがない」の理由は、費用だった

イオンスタイルオンラインと、あみあみの 2 サイトは、取得の間隔を縮められない、と考えていました。理由は、取得にかかる費用が重いこと。つまり、古い単価を前提にした結論です。それを、もう一度、測ってみることにしました。

3 つの設定で、同じページを取った

取得の設定は、3 段階あります。単純に取る(1 クレジット)、ブラウザで描画してから取る(render、5 クレジット)、さらに強力な経路で描画して取る(super+render、25 クレジット)。イオンの一覧ページを、この 3 つで取って、中身を比べました。2 ページを、それぞれ 3 回、くり返しています。

設定クレジットサイズ(バイト)商品価格
単純に取る1649,73420 件25 件
render5663,02620 件25 件
super+render25669,51020 件25 件

3 つとも、取れた商品も価格も、まったく同じでした。つまり、描画(render)は、まるごと無駄だったことになります。1 クレジットで足りるものに、25 クレジット払っていました。

直し方

ただし、あみあみの間隔は、15 分のままにしました。その日、タイムアウトを 2 回出していて、1 回の実行が 284 秒もかかっていたからです。取得全体も混み合ってきていたので、触らないと決めました。測ったのは「描画が要るか」で、「間隔を縮めても大丈夫か」ではありません。

分かったこと

「しょうがない」は、いつ測った数字に、もとづいているか。

この記事の数字は、コミット(2026年9月7日)の記録から取りました。「4,600」は、コミットの記載で、25 クレジット × 2 ページ × 96 回(15 分おき)の計算(4,800)とは、少しずれています。

Firestore の費用を円で出したら、読み取りの 91% が 1 か所だった:キャッシュを入れても効かなかった理由

2026 年 10 月 17 日の記録。

iOS アプリ「トレカダッシュ」の裏側は、Firebase の Firestore(データベース)と、定期的に動く処理でできています。費用は、Gemini(AI)と取得サービスの分は、毎日、円で出して見ていました。ところが、Firestore の費用は、レポートに載せていませんでした。

9 月 4 日の深夜に、はじめて円で出してみました。結果は、Gemini とほぼ同額でした。この記事は、そこから読み取りの 91% が 1 か所に集中していると分かり、キャッシュを入れても効かず、直し直した記録です。

数字は、2026 年 9 月 4 日の作業の記録から書いています。価格は、東京リージョンの実際の単価です。

円で出したら、誰も見ていなかった費用が出てきた

Firestore の読み取りを、日ごとに円にしました。

日読み取り書き込み1 日の費用
8 月 28 日218 万件12 万件226 円(月 6,777 円)
9 月 1 日366 万件13.4 万件368 円(月 11,028 円)
9 月 3 日421 万件16.2 万件427 円(月 12,810 円)

無料の枠は、1 日 5 万件の読み取りまでです。その84 倍を読んでいました。しかも、1 週間で 89% 増えていて、記事が増えるほど増える形でした。

円で出していない費用は、ないのと同じでした。月に約 1 万 3 千円かかっていたのに、レポートに載っていないというだけで、誰も気にしていませんでした。

計器を入れたら、3 分で分かった

どこで読んでいるのかを知るために、「どの処理が、どのコレクション(データの置き場)を、どれだけ読んだか」を数える計器を入れました。入れたその場で、答えが出ました。

8,725 件  X 監視 | products(商品)    ← 測定した 9,593 件の 91%
  197 件  X 監視 | news_items(記事)

X の監視は、記事を 1 件処理するたびに、AI に「どの商品の話か」を聞きます。その候補を作るために、ジャンルの商品を、毎回、全部読み直していました。ポケモンのジャンルは商品が 693 件あります。記事 1 件の照合が、693 件の読み取りです。

コードを読んで探していたら、もっと時間がかかったはずです。探す前に、数える。計器を 1 つ足しただけで、読み取りの 91% が 1 か所だと分かりました。

最初のキャッシュは、ほとんど効かなかった

直し方は単純です。商品の一覧を、一度読んだら覚えておく(キャッシュ)。最初は 60 秒覚えさせました。ところが、ほとんど効きませんでした。

理由は、X の監視が、最大 32 アカウントを同時に処理していることでした(Promise.all)。コードは、こう動いていました。

  1. 1 件目が、「キャッシュに無い」と判断して、読みに行く。
  2. 読み終わる前に、残りの 31 件も、同じ「キャッシュに無い」を見る。
  3. 全員が、同じ 693 件を、同時に読みに行く。

実測では、23 秒に 1 回、読み直していました。キャッシュは「読み終わった結果」を覚えるもので、読んでいる最中の人は、まだ何も覚えていないのです。

直し方は、結果ではなく、「読み込み中」という約束(Promise)そのものを覚えることでした。1 件目が読み始めたら、その約束を置いておく。残りの 31 件は、新しく読みに行かず、同じ約束を待つ。同時に呼ばれても、読み込みは 1 本になります。

// 考え方を書いた例です。結果ではなく、読み込み中の約束を覚える
cache.set(genre, { at: Date.now(), promise: loadProducts(genre) });
// 呼び出しは、いつも cache.get(genre).promise を待つ

これに合わせて、3 つのことも決めました。

静かな時間帯で測ると、1 日に換算して、507 万件が 27.7 万件になりました。

もう 1 つ:5 分おきに 3,085 件読んで、使うのは 146 件だった

商品のキャッシュを入れたあと、計器の一番上に出てきたのが、別の処理でした。楽天ブックスの在庫を確かめ直す処理が、5 分おきに、こういう問い合わせをしていました。

news_items where saleType in ["reservation", "guerrilla"]

返ってきた 3,085 件を、プログラムの側で「楽天ブックスのものだけ」に絞っていました。実際の対象は 146 件で、95% が無駄です。1 日 288 回なので、約 88.8 万件、月に約 2,400 円でした。

直し方は、2 つです。

読み取りは、1 日 88.8 万件から約 7.9 万件へ、91% 減りました。

分かったこと

円で出していない費用は、存在しないのと同じ。

Gemini の費用を削る前に、計器を疑った:単価・内訳・キャッシュの 3 つが壊れていて、キャッシュの条件は測り直すと変わっていた

2026 年 10 月 17 日の記録。

iOS アプリ「トレカダッシュ」は、記事の判定や商品の照合に、Google の Gemini(AI)を使っています。使った分だけお金がかかるので、費用を減らす作業を何度かしました。この記事は、その中の 2 つの回の記録です。

数字は、2026 年 9 月 4 日と 10 月 16 日の作業の記録から書いています。使ったのは gemini-3.1-flash-lite です。

計器が、3 か所とも壊れていた

「費用を削って」と言われて、最初にしたのは、削ることではなく、費用の計測が合っているかの確認でした。合っていない計器で削ると、効かない場所を削って「なぜ下がらない」となります。実際に、3 つの欠陥が見つかりました。

計器の欠陥起きていたこと
単価が違うコード上の単価は、100 万トークンあたり入力 $0.10・出力 $0.40。実際の価格は 入力 $0.25・出力 $1.50。費用を約 1/2.6 に、少なく表示していた。
内訳のラベルが潰れていた用途別の集計が、実際には存在しない名前のキー(params.system)を見ていた(正しくは system_instruction)。31 種類の用途が、全部 1 つの塊になり、どの指示文が高いのか、一度も読めていなかった。
キャッシュを数えていない入力の 31% はキャッシュの命中で、単価が 1/10 なのに、全部を定価で数えていた。約 4 割、多く見積もっていた。

もう 1 つ、見かけ上の「費用 2 位」が、本番の問題ではありませんでした。「ジョブ外」という名前の項目が 1 日 195 円で 2 位でしたが、これは私自身が、手で実行した分でした。本番の実力を見るときは、手動で動かした分を、必ず除きます。

直した計器で測ると、この時点で、1 日約 606 円(月 18,168 円)。キャッシュの分を引いた実質は、月約 13,200 円でした。費用のほとんどは入力トークンで、中身は、毎回送り直す指示文と候補の一覧です。

9 月の測定:キャッシュは約 8,000 トークンを超えてから

Gemini には、同じ長い指示文を繰り返し送ると、2 回目から安くなる仕組み(暗黙のキャッシュ)があります。命中した分の単価は 1/10 です。そこで、同じ指示文を 3 回続けて送って、どこから効くかを測りました。

指示文の長さ(入力トークン)キャッシュの命中
5,2090
8,4904,079
17,07012,263

当時の結論は、「約 8,000 トークンを超えてから効く」でした。実質のトークンは、入力 − 0.9 × 命中で計算できます。

ここで、無理に指示文を長くして基準に乗せるのは、逆に高くつく、と分かりました。4,200 トークンの指示文を 8,500 に膨らませると、実質は 4,814 トークンで、元より多いのです。キャッシュが得になるのは、元から大きい指示文だけです。

10 月の測り直し:条件は、約 4,000 トークンだった

もう一度、本番のキーで、同じ測定をしました。今度は、interactions API という呼び出し口で測りました。結果は、9 月と違いました。

指示文の長さ(入力トークン)キャッシュの命中
1,7700
4,0932,031(2 回目から)
7,0174,076

約 4,000 トークンから効き、先頭の約 2,038 トークン単位で命中していました。9 月は「8,000 から」、10 月は「4,000 から」。なぜ食い違うのかは、確かめていません。測り方の違いや、サービス側の変更が考えられますが、どれが原因かは分かっていません。分かるのは、「キャッシュの条件は、固定の事実ではなく、測り直すもの」ということです。

本番の指示文は、1,000〜2,500 トークンでした。先頭の固定部分が基準に届かず、命中は 1 日中 0 でした。入力トークンが費用のほぼ全部(出力は約 1%)なので、キャッシュが効けば、大きな節約に見えました。

指示文を水増ししてみた。判定が 9% ずれた

「判定に影響しない注記」を足して、指示文を約 2,300 トークンに伸ばし、基準に乗せる案を試しました。実データ 90 件で、元の指示文と答えを比べました。

精度が落ちるのに、命中も取れない。この案は取り下げて、本番には出していません。「影響しない」はずの文章でも、AI の答えは変わります。確かめずに足してはいけません。

いま残っている、いちばん大きい項目

実データで内訳を見ると、最大の単独項目は、商品の照合でした。1 回あたり平均 7,151 入力トークン、1 日 297 回で、約 83 円です。候補の絞り込み、まとめて聞く、AI に聞かずに済ませる、は、すでに入れてあります。これ以上は、候補を減らす、つまり精度を下げる方向になるので、見送っています。

削るなら、指示文を短くする方向です。その場合は、1 本ずつ、実データで判定が一致するかを確かめます。末尾の水増しで、キャッシュを狙いません。

分かったこと

費用を削る前に、削る道具を疑う。

それぞれの要点

ほかの記録は、Notes の一覧から、テーマごとに読めます。トレカダッシュそのものは、作品ページに書いています。

ほかの記録

コメント

感想や、記事の間違いの指摘は、ここへ。書くには、GitHub のアカウントが要ります。

押すと、giscus(giscus.app)と GitHub に接続します。押さなければ、接続しません。詳しくはプライバシーポリシーに書いています。