「ダッシュボードを開くたびに同じクエリが走って、地味にコストが積み上がっているのでは……」
中小ECのデータ基盤を見ていると、こうした不安をよく耳にします。Looker Studioのダッシュボードや日次レポートでは、まったく同じSQLが何度も実行される場面が珍しくありません。その都度フルでスキャンされていたら、たしかにもったいない話です。
ところがBigQueryには、同じクエリの結果を一定の条件下で「キャッシュ」から返し、その分のスキャン料金を取らないという仕組みがあります。これを理解しているかどうかで、月々の請求額もダッシュボードの体感速度も変わってきます。
この記事では、BigQueryのクエリキャッシュがどう動くのか、どんなときに効いてどんなときに効かないのか、そしてキャッシュを味方につける書き方を、中小ECの運用目線で整理します。
はじめに:同じクエリは、2回目から無料になる
BigQueryのオンデマンド課金は、クエリが読み込んだ(スキャンした)データ量で決まります。1TBあたり数ドル、毎月最初の1TBは無料、というのが基本の形です(2026年時点の目安。料金は変わりうるので最新は公式で確認してください)。コストを抑える王道は「読むデータを減らす」ことに尽きます。
ここで効いてくるのがクエリキャッシュです。BigQueryは、過去に実行したクエリの結果を一時的に保存しておき、まったく同じクエリが来たときにその保存済み結果をそのまま返すことがあります。このときデータのスキャンは発生しないため、キャッシュヒットしたクエリのスキャン料金はかかりません。しかも結果が即座に返るので、待ち時間もほぼゼロになります。
つまり「同じクエリは、条件さえ揃えば2回目以降が無料かつ高速」というのが、キャッシュの一番おいしいところです。コスト削減の文脈では、列の絞り込みやパーティション設計とあわせて、ぜひ押さえておきたい性質です。
なお、料金体系やキャッシュの細かい挙動は変わる可能性があります。本記事は2026年時点の一般的な理解にもとづいていますので、実際の運用前には必ず公式ドキュメントの最新情報を確認してください。
関連して、スキャン量そのものを減らす実践はBigQueryのクエリコストを月1万円以下に抑える7つの実践テクニックでまとめています。あわせて読むと、コスト管理の全体像がつかめます。
キャッシュの仕組み
BigQueryのクエリキャッシュは、ざっくり言うと「クエリのテキストと、参照しているデータの状態」をキーにして結果を再利用する仕組みです。
クエリを実行すると、その結果は一時的なキャッシュ領域に保存されます。次に同じSQLが投げられたとき、BigQueryは「このクエリの結果はキャッシュにあるか」「参照先のテーブルはあれから変わっていないか」を確認します。条件を満たせば、保存済みの結果をそのまま返します。
ポイントは2つあります。
キャッシュはユーザー単位・プロジェクト単位で持つ
キャッシュは基本的にユーザーごとに保持されます。自分が前に実行したクエリの結果が、自分の次の実行で再利用される、というのが基本のイメージです。チーム全員で1つのキャッシュを共有するわけではない、という点は頭に入れておくとよいでしょう。
結果には有効期限がある
キャッシュされた結果は永遠に残るわけではなく、一定時間が経つと無効になります。また、後述するようにテーブルが更新されると、その時点でキャッシュは使われなくなります。「昨日のクエリ結果がいつまでも返ってくる」といった事故は、基本的には起きない設計です。
挙動の細部(保持期間や対象範囲)はサービス側の仕様変更で変わりうるため、厳密な数値は最新の公式ドキュメントで確認してください。
効く条件・効かない条件
キャッシュは便利ですが、「いつでも効く」わけではありません。効く条件と効かない条件を把握しておくと、無駄な期待やデバッグの混乱を避けられます。
効きやすいのは、次のようなケースです。
- まったく同じSQLテキストを、参照先テーブルが変わらないうちに再実行したとき
- Looker Studioや定型レポートで、同じ集計クエリが短時間に繰り返されるとき
逆に、次のような場合はキャッシュが効きません。
- クエリ結果が毎回変わる関数を使っている:
CURRENT_TIMESTAMP()やCURRENT_DATE()、RAND()のような、実行のたびに結果が変わりうる関数(非決定的な関数)を含むクエリは、原則キャッシュ対象外です。 - 参照しているテーブルが更新された:クエリが読むテーブルに新しい行が入る、既存の行が変わるなど、データが変化するとキャッシュは無効になります。データが変わったのに古い結果を返さないための、当然の仕組みです。
- ワイルドカードや外部データを参照している:
_TABLE_SUFFIXを使ったワイルドカードテーブルや、外部テーブル(GCS上のファイルなど)を参照するクエリは、キャッシュが効かない場合があります。GA4エクスポートをevents_*のように横断して読むクエリは、この影響を受けやすい代表例です。 - クエリテキストが少しでも違う:空白やコメント、列の並び順が違うだけで「別のクエリ」と見なされ、キャッシュにヒットしないことがあります。
- 宛先テーブルを指定している、あるいはキャッシュを使わない設定にしている:結果を特定のテーブルに書き出す指定や、キャッシュ無効の設定をしている場合は対象外です。
特に中小ECで効いてくるのが「テーブル更新」と「ワイルドカード」の2点です。受注テーブルに日中ずっと新しい注文が入り続ける構成だと、その受注テーブルを直接読むクエリはキャッシュが効きにくくなります。GA4の生ログを毎回横断スキャンするクエリも同様です。
キャッシュを活かす書き方
では、どうすればキャッシュを味方につけられるのでしょうか。難しいテクニックは要りません。基本は「同じクエリを、データが落ち着いたタイミングで投げる」ことです。
クエリテキストを揃える
ダッシュボードやレポートで使うクエリは、できるだけ固定して使い回します。実行のたびに日付リテラルを書き換えるのではなく、保存済みのクエリやビューとして定義しておくと、同一テキストが再利用されやすくなります。
-- 同じテキストで繰り返し実行されることを意識する
-- 日付をハードコードする場合は、その期間中ずっと同じSQLになる
SELECT
order_date,
COUNT(*) AS order_count,
SUM(total_amount) AS revenue
FROM `myshop.sales.orders_daily`
WHERE order_date BETWEEN '2026-07-01' AND '2026-07-29'
GROUP BY order_date
ORDER BY order_date;
非決定的な関数を避けられないか考える
「直近7日間」のような相対期間を CURRENT_DATE() で書くと、毎回キャッシュが切れます。日次バッチで「対象期間を一度だけ計算して固定値として埋め込む」設計にすれば、その日のうちは同じSQLになり、キャッシュが効きやすくなります。
-- キャッシュが効きにくい例:実行のたびに基準日が動く
SELECT order_date, SUM(total_amount) AS revenue
FROM `myshop.sales.orders_daily`
WHERE order_date >= DATE_SUB(CURRENT_DATE(), INTERVAL 7 DAY)
GROUP BY order_date;
-- 効きやすい例:期間を固定リテラルにして同一テキストにする
SELECT order_date, SUM(total_amount) AS revenue
FROM `myshop.sales.orders_daily`
WHERE order_date BETWEEN '2026-07-23' AND '2026-07-29'
GROUP BY order_date;
更新タイミングと参照タイミングをずらす
テーブルが更新されるとキャッシュは無効になります。逆に言えば、更新が終わって落ち着いた集計済みテーブルを参照すれば、その日のあいだは何度読んでもキャッシュが効きやすくなります。次に紹介する中間テーブルが、ここで効いてきます。
中間テーブルでキャッシュとスキャンの両方を効かせる
中小ECで一番効果が出やすいのが、中間集計テーブル(データマート)を挟む構成です。
GA4の生ログや受注の明細をダッシュボードから毎回直接読むと、ワイルドカードや頻繁な更新でキャッシュが効きにくく、スキャン量も大きくなります。そこで、日次バッチで重い集計を一度だけ実行し、結果を小さな集計済みテーブルに書き出しておきます。
-- 日次バッチ:重い集計は1日1回だけ実行して中間テーブルへ
CREATE OR REPLACE TABLE `myshop.mart.daily_kpi` AS
SELECT
order_date,
COUNT(DISTINCT order_id) AS order_count,
COUNT(DISTINCT customer_id) AS buyer_count,
SUM(total_amount) AS revenue
FROM `myshop.sales.orders`
GROUP BY order_date;
こうしておくと、ダッシュボードは myshop.mart.daily_kpi という小さなテーブルだけを読めばよくなります。このテーブルはバッチ実行後は更新されないので、日中に何度開いてもキャッシュが効きやすく、スキャン量自体も生ログより桁違いに小さくなります。「スキャンを減らす」と「キャッシュを効かせる」の両方を同時に満たせるのが、この構成の強みです。
中間テーブルを毎日自動で作る仕組みは、スケジュールクエリやバッチで組めます。
マテリアライズドビューとの使い分け
「結果を使い回す」という意味で、クエリキャッシュとよく比較されるのがマテリアライズドビュー(マテビュー)です。役割が似ているようで、性質はかなり違います。
クエリキャッシュは、あくまで「直前に実行した同一クエリの結果を一時的に再利用する」仕組みです。自動で効く反面、テーブルが更新されたりクエリテキストが変わったりすると簡単に外れます。明示的に管理するものではありません。
一方マテリアライズドビューは、集計結果を物理的に保持しておき、元テーブルの更新に追従して自動で差分更新してくれる仕組みです。元データが変わってもビューが最新化され、クエリ側はその最新結果を読めます。「頻繁に更新されるテーブルに対して、いつも最新の集計を速く返したい」という用途に向いています。
ざっくり整理すると、こうなります。
- クエリキャッシュ:意識せず自動で効く。同じクエリの短時間の繰り返しに有効。更新や差分には弱い。設定不要・追加コストなし。
- 中間テーブル:自分で作って管理する。1日1回など決まったタイミングで更新。シンプルで挙動が読みやすい。
- マテリアライズドビュー:自動で最新化される。頻繁に更新されるデータでも速く最新結果を返せる。維持に応じたコストがかかる。
中小ECでまず取り組むなら、「中間テーブル+キャッシュ」の組み合わせが分かりやすく、コストも読みやすいです。更新が頻繁で、かつ常に最新の集計を速く出したい場面が出てきたら、マテリアライズドビューを検討するとよいでしょう。マテビューの具体的な使い方はBigQueryのマテリアライズドビューでGA4集計を高速化するで解説しています。
なお、マテビューや差分更新の仕様、対応している集計の種類はアップデートが入りやすい領域です。導入前に最新の公式ドキュメントで確認してください。
まとめ
BigQueryのクエリキャッシュは、設定不要で自動的に効き、ヒットすればスキャン料金もかからない、コスト効率の心強い味方です。ポイントを振り返ります。
- 同じクエリは、条件が揃えば2回目以降がスキャン無料かつ高速になる
- 非決定的な関数、テーブル更新、ワイルドカード、テキストの差異などでキャッシュは外れる
- 期間を固定リテラルにし、同一テキストで投げるとヒットしやすい
- 中間集計テーブルを挟むと、スキャン削減とキャッシュの両方を同時に効かせられる
- 頻繁な更新でも常に最新を速く返したいなら、マテリアライズドビューを検討する
「同じものは2回読まない」という発想は、コスト最適化の基本でもあります。まずは自社のダッシュボードや日次レポートで、どのクエリが毎回フルスキャンしているかを見直すところから始めてみてください。中間テーブルを1枚挟むだけでも、請求額と体感速度がはっきり変わるはずです。
仕様の細部は変化しますので、実装の前には必ず最新の公式ドキュメントで挙動と料金を確認することをおすすめします。