【やらかしエピソード】なんですか、この読み込み件数の多さ...
この記事は CA Tech Lounge Advent Calendar 2025 の23日目の記事です。
前回は yomi_tiger さんによる「Swiftのsomeってなに?」でした。
Firestore Enterprise Edition(以降「Firestore EE」)にスクレイピングしたデータを Insert するというコードを書いていました。
そもそも Firestore EE とは?
今回使う Firestore EE は MongoDB 互換の Firestore であり、MongoDB Atlas Serverless からの移行先として採用しました。
Atlas Serverless の特徴でもあったリクエスト(Firestore EEの場合はユニット)ごとの課金体系であり、個人開発のような低価格性が求められる場面で大きな利点があります。
やったこと(やらかしたこと)
スクレイピングしたデータを書き込む(IDが重複しているときは、上書きをする)コードを書きました。
中身でやっていることはAPIの名前の通り、「指定されたコレクションから一個ドキュメントを探して、見つかったらアップデートする」という単純なものです。
const result = await db.collections("shop").findOneAndUpdate(
{ slug: data.shop.id },
{ $setOnInsert: { name: data.shop.name } },
{ upsert: true, returnDocument: "after" }
)
1秒間に1回のリクエストだったためと、複雑なクエリではないことから、大きく無料枠を超えることは起こりづらいと考えていました。
17時半、少し早めの夕食を食べるため前述のコードを実行したうえで、席を離れました。
21時に戻り登録状況がどのようになっているか Google Cloud のクエリ統計を確認したところ、1分で2.5万件を超える Read が発生しており、只事ではないと考え、一時停止。
そこから問題の調査をすることになりました。

横軸が時間、右軸がクエリあたりのスキャン済みドキュメント数
原因
原因は非常に単純明快です。課金体系の認識の甘さが原因でした。
Firestore Standard Edition はドキュメントサイズにかかわらず、「どれだけ読み込まれたか・どれだけ書き込んだか」で課金されます。
対して、Firestore EE は
読み取りユニット: データベースからデータを読み取るときに処理されるデータ(ドキュメントまたはインデックス)を表し、4 KiB トランシェで計算されます。
という表記から分かる通り、どれだけの量を読み取ったかで課金されます。
今回の場合は shop コレクションに蓄積された情報を、すべて読み取っていることから、通常の書き込みに加えて、異常な量の読み取りユニットがかかる結果になりました。
対策
対策方法も非常に明快です。インデックスを張りましょう。
また、Firestore Query Explain を利用すれば 1RU でどれくらいのコストがかかるか見積もることができます。
ある程度データが入った状態で、何か新しいクエリを叩きたくなったら、最初にこれだけはやっておきましょう。
今回の場合はまっさらなデータベースにInsertしていくことから、Query Explain を使用せず、インデックスを張る形で対応しました。

横軸が時間、右軸がクエリあたりのスキャン済みドキュメント数、左軸がクエリあたりのスキャン済みインデックスエントリ数
クエリあたりのスキャン済みドキュメント数が減ることで、読み取りユニットの消費も落ち着きました。

ダメージ
BigQueryで150万円溶かした人も居たりしますが、今回の場合は
Read Unit: 25.2万件程度
Write Unit: 6.8万件程度
5円のダメージでした
安いですね、払います。
この発見が遅れれば遅れるほど、データ数が多い状態でクエリを叩けばたたくほど、青天井の金額になってたことを考えると恐ろしさを感じます。
課金体系には気を付けよう。そう心を引き締める良い勉強になりました。
Discussion