「BigQueryって、Googleの大企業向けツールでしょう? うちみたいな小さなショップが使ったら、請求書を見て卒倒するのでは……」

データ基盤の相談をいただくと、最初によく聞かれるのがコストの不安です。たしかに「ペタバイト級」「Google規模」みたいな言葉を聞くと、料金もそれ相応に思えてきますよね。

でも実際のところ、中小ECや個人事業主の規模であれば、BigQueryは月1万円以下、場合によっては数百円〜無料枠内で十分に運用できます。鍵になるのは「BigQueryの課金がどう決まるか」を理解して、無駄なスキャンを避けることです。

この記事では、その仕組みと、コストを抑えるための実践テクニックを7つ、具体的なクエリ例つきで紹介します。

はじめに:BigQueryの課金は「スキャン量」で決まる

BigQueryの代表的な料金モデルはオンデマンド課金です。これは「クエリが読み込んだ(スキャンした)データ量」に対して課金される仕組みです。テーブルの保存量でも、クエリの実行回数でもなく、1回のクエリで何バイト読んだかがコストを決めます。

目安として、オンデマンド課金は 1TB(テラバイト)のスキャンあたり約 $6.25(東京リージョンなどで多少前後します)。さらに、毎月最初の1TBは無料という枠もあります。

💡 補足

料金は地域や時期によって変わります。本記事の金額は 2026年時点のおおよその目安 です。実際に契約・運用する前に、必ず公式の最新料金を確認してください。「1TB=$6.25・最初の1TB無料」も将来変わる可能性があります。

ここで大事なのは、**「1TBも読まなければ、その分安い」**という当たり前の事実です。中小ECのデータ(受注・会員・GA4のアクセスログなど)は、設計しだいで1クエリのスキャン量を数MB〜数百MBに抑えられます。仮に毎日10クエリ、各500MBスキャンしても、月間で約150GB。これは無料枠の1TBに収まり、実質0円です。

つまりBigQueryのコスト管理とは、**「いかに読むデータを減らすか」**に尽きます。以下の7つはすべて、この一点に向けたテクニックです。

#テクニック主な効果
1SELECT * をやめて列を絞る不要な列のスキャンを丸ごと削減
2パーティション+クラスタリング日付・条件で読む範囲を限定
3GA4は _TABLE_SUFFIX で日付絞り全期間スキャンを回避
4中間集計テーブル・マテビュー同じ重い集計の再スキャンを防ぐ
5--dry_run・プレビューで事前見積もり実行前にコストを把握
6カスタムコスト上限・quota事故的な高額請求を物理的に防ぐ
7INFORMATION_SCHEMAで監視どのクエリが高いか可視化

テク1:SELECT * をやめて、必要な列だけ選ぶ

いちばん効果が大きく、いちばん簡単なのがこれです。

BigQueryは列指向(カラムナ)ストレージを採用しています。データを「行のかたまり」ではなく「列ごと」にまとめて保存しているため、クエリで指定した列だけを読み込めます。逆に言うと、SELECT * を書くと全列をスキャンしてしまい、使わない列の分まで課金されます。

-- NG: 50列あるテーブルから全列を読む
SELECT *
FROM `myshop.sales.orders`
WHERE order_date = '2026-06-01';

-- OK: 必要な3列だけ読む
SELECT order_id, customer_id, total_amount
FROM `myshop.sales.orders`
WHERE order_date = '2026-06-01';

受注テーブルに住所・備考・各種フラグなど50列あったとして、集計に使うのが3列だけなら、スキャン量はざっくり10分の1以下になることも珍しくありません。「とりあえず全部見る」癖をやめるだけで、コストは大きく下がります。

⚠️ 注意

LIMIT を付けてもスキャン量は減りません。SELECT * FROM table LIMIT 10 は、表示こそ10行でも、内部では対象列を全件スキャンしています。コストを下げたいなら、減らすのは行数ではなくと、次に説明するパーティションです。

テク2:パーティション(日付)+クラスタリングでスキャン範囲を絞る

テク1が「列を絞る」なら、テク2は「行(読む範囲)を絞る」です。

パーティション分割は、テーブルを日付などの単位で物理的に区切っておく仕組みです。日付でパーティションを切っておけば、WHERE で日付を指定したときに、その日のブロックだけを読み込みます。過去2年分のデータがあっても、1日分しか読まなければ課金も1日分です。

-- 受注テーブルを order_date で日付パーティション化し、
-- さらに customer_id でクラスタリングして作成
CREATE TABLE `myshop.sales.orders_partitioned`
PARTITION BY DATE(order_date)
CLUSTER BY customer_id AS
SELECT * FROM `myshop.sales.orders`;

こうしておけば、次のクエリは6月分のパーティションだけをスキャンします。

SELECT customer_id, SUM(total_amount) AS sales
FROM `myshop.sales.orders_partitioned`
WHERE order_date BETWEEN '2026-06-01' AND '2026-06-30'
GROUP BY customer_id;

クラスタリングは、パーティション内のデータをさらに指定列(上の例では customer_id)で並べ替えておく仕組みです。特定の顧客や商品で絞り込むクエリが多いなら、クラスタリングを併用するとスキャン量がさらに減ります。

💡 補足

パーティション列を WHERE で指定し忘れると、全期間をスキャンしてしまい意味がありません。「日付パーティションを切ったテーブルは、必ず日付で絞る」をチームの習慣にしておくと安全です。require_partition_filter オプションを有効にすると、日付指定なしのクエリをエラーにして事故を防げます。

テク3:GA4の events_ テーブルは _TABLE_SUFFIX で日付を絞る

中小ECでBigQueryを使う大きな理由のひとつが、GA4(Google アナリティクス4)のデータ連携です。GA4をBigQueryにエクスポートすると、events_20260601events_20260602 …… のように日付ごとのテーブルが大量に作られます。

これをまとめて扱うときに events_*(ワイルドカード)を使いますが、絞り込みを忘れると全期間の全テーブルをスキャンしてしまい、一気にコストが膨らみます。ここで使うのが _TABLE_SUFFIX です。

-- NG: 全期間のイベントを読んでしまう
SELECT event_name, COUNT(*) AS cnt
FROM `myshop.analytics_123456.events_*`
GROUP BY event_name;

-- OK: _TABLE_SUFFIX で6月分だけに絞る
SELECT event_name, COUNT(*) AS cnt
FROM `myshop.analytics_123456.events_*`
WHERE _TABLE_SUFFIX BETWEEN '20260601' AND '20260630'
GROUP BY event_name;

_TABLE_SUFFIX は、ワイルドカード * に当たる部分(ここでは日付)を表す擬似列です。これで絞ると、対象期間のテーブルだけが読まれます。GA4は1日のイベント数が多くなりがちなので、この一手の効果はとても大きいです。

GA4データをLooker Studioで可視化する場合のコストの考え方は、Looker StudioからBigQueryに接続するときのコスト最小化で別途まとめています。ダッシュボードは更新のたびにクエリが走るので、ここの設計はコストに直結します。

テク4:中間集計テーブル・マテリアライズドビューで再スキャンを防ぐ

同じ重い集計を、レポートのたびに何度も実行していませんか。

たとえば「日別・商品別の売上サマリー」を毎回 raw データから計算していると、その都度フルスキャンが走ります。これを中間集計テーブルとして1日1回だけ作り直しておけば、レポートはその小さなサマリーを読むだけで済みます。

-- 1日1回だけ実行して、日別サマリーを作り直す
CREATE OR REPLACE TABLE `myshop.mart.daily_sales` AS
SELECT
  order_date,
  product_id,
  COUNT(*)        AS order_count,
  SUM(total_amount) AS sales
FROM `myshop.sales.orders_partitioned`
GROUP BY order_date, product_id;

集計後のテーブルは元データよりずっと小さいので、ダッシュボードや分析からの読み取りコストが激減します。「重い計算は1回、参照は何度でも安く」という考え方です。

さらに自動化したいなら、マテリアライズドビューという選択肢もあります。これは元テーブルの更新に追従して結果を自動でキャッシュしてくれる仕組みで、手動の作り直しが不要になります。まずは中間集計テーブルから始めて、運用が固まってきたらマテビューを検討する、という順番がおすすめです。

💡 補足

BigQueryにはクエリ結果のキャッシュもあり、まったく同じクエリを実行すると(元データが変わっていなければ)キャッシュが返り、その分は課金されません。ただしクエリ文が少しでも違うとキャッシュは効かないので、コスト削減の本命はあくまで中間集計です。

テク5:--dry_run とプレビューで、実行前にスキャン量を見積もる

「このクエリ、実行したらいくらかかるんだろう」という不安は、実行前に見積もることで解消できます。

bq コマンドラインツールの --dry_run を使うと、実際には実行せずに「このクエリが何バイトスキャンするか」だけを教えてくれます。

# クエリを実行せず、スキャン予定バイト数だけを確認する
bq query --use_legacy_sql=false --dry_run \
'SELECT order_id, total_amount
 FROM `myshop.sales.orders_partitioned`
 WHERE order_date = "2026-06-01"'

実行すると Query successfully validated. ... this query will process N bytes of data. のような形でバイト数が返ります。あとは「N バイト ÷ 1TB × 単価(目安 $6.25)」でおおよそのコストが計算できます。

コマンドが苦手な方は、BigQueryのWeb画面(コンソール)のクエリエディタ右上にも、実行前に「このクエリは○○ MB を処理します」という見積もりが自動で表示されます。この数字を実行前に必ず見る習慣をつけるだけで、想定外の高額クエリを未然に防げます。

SQLを書くこと自体に不慣れな場合は、自然言語でSQLを書く(Gemini in BigQuery)で紹介しているAI支援機能も、まずは安全なクエリの形を学ぶ助けになります。

テク6:カスタムコスト上限とquotaで「物理的に」上限を設ける

テクニックを駆使しても、操作ミスや想定外のクエリでコストが跳ねる可能性はゼロにはなりません。そこで、仕組みとして上限を設けておくのが保険になります。

BigQueryには、以下のような上限設定があります。

  • クエリ単位の最大スキャン量上限:1クエリで読めるバイト数の上限を設定。これを超えるクエリはエラーになり、実行されません。巨大クエリの暴発を防げます。
  • プロジェクト/ユーザー単位の1日あたりquota:1日にスキャンできる総量に上限を設定。「1日◯TBまで」と決めておけば、最悪のケースでも課金額に天井ができます。

これらはGoogle Cloudの管理画面(IAMと管理 → 割り当て)から設定できます。

⚠️ 注意

上限に達するとクエリがエラーになるため、本当に必要な分析まで止まってしまうこともあります。上限値は、ふだんの使用量を把握したうえで「平常時の数倍」くらいの余裕をもって設定するのがコツです。テク7の可視化で実際の使用量を確認してから決めると失敗しません。

中小ECの規模なら、こうした上限を「お守り」として設定しておくだけで、コスト不安の大部分は消えます。「最悪でもこれ以上はかからない」という安心感は、運用を続けるうえでとても大きいです。

テク7:INFORMATION_SCHEMAでコストを可視化・監視する

最後は、実際に何にいくらかかっているかを見える化するテクニックです。BigQueryは自身の実行履歴(メタデータ)を INFORMATION_SCHEMA というビューで提供しており、「どのクエリが・どれだけスキャンしたか」を自分で集計できます。

-- 直近7日間で、スキャン量の多いクエリ上位20件を調べる
SELECT
  user_email,
  job_id,
  ROUND(total_bytes_processed / POW(1024, 3), 2) AS scanned_gb,
  ROUND(total_bytes_processed / POW(1024, 4) * 6.25, 4) AS est_cost_usd,
  query
FROM `region-asia-northeast1`.INFORMATION_SCHEMA.JOBS
WHERE creation_time >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 7 DAY)
  AND job_type = 'QUERY'
  AND state = 'DONE'
ORDER BY total_bytes_processed DESC
LIMIT 20;

total_bytes_processed がスキャン量で、それを単価(目安 $6.25/TB)で掛ければ概算コストになります。これで「特定のダッシュボードが毎時走って高い」「誰かの SELECT * が重い」といった犯人を特定できます。

💡 補足

上のクエリの region-asia-northeast1 は東京リージョンの例です。データの保存リージョンに合わせて変えてください。また、この監視クエリ自体のスキャンは(メタデータなので)ごくわずかで、コスト心配はほぼいりません。月初に一度この棚卸しをするだけで、コスト体質はかなり改善します。

補足:料金体系の選び方(オンデマンド vs 容量ベース)

BigQueryの課金には、ここまで前提にしてきたオンデマンド課金のほかに、**容量ベース課金(Editions/スロット予約)**があります。後者は「計算リソース(スロット)を時間単位で確保し、その分を払う」モデルで、スキャン量ではなく確保した計算枠に対して課金されます。

ざっくりした使い分けの目安はこうです。

  • オンデマンド:使った分だけ・無料枠あり。クエリ量が読めない、あるいは少ない小〜中規模に向く
  • 容量ベース:大量のクエリが安定的に走り、毎月のスキャン量が大きく予測できる大規模に向く。月額のコストは固定化しやすい。

中小ECや個人事業主の規模であれば、まずはオンデマンド一択で問題ありません。無料枠1TBの恩恵が大きく、使わない月は安く済むからです。クエリが増えて毎月コンスタントに数十TB以上をスキャンするようになってきたら、そこで初めて容量ベースを検討する、という順番が無駄がありません。

まとめ

BigQueryのコストは「スキャン量」で決まる――この一点さえ押さえれば、中小ECの規模で月1万円以下に抑えるのは十分に現実的です。最後に7つを振り返ります。

#テクニックひとことで言うと
1列を絞るSELECT * をやめる
2パーティション+クラスタ読む範囲を日付・条件で限定
3GA4は _TABLE_SUFFIX全期間スキャンを回避
4中間集計・マテビュー重い計算は1回だけ
5--dry_run・プレビュー実行前に見積もる
6コスト上限・quota事故の天井を作る
7INFORMATION_SCHEMA高いクエリを可視化

最初の一歩としておすすめなのは、**テク1(列を絞る)とテク5(実行前に見積もる)**です。この2つは今日からすぐ実践できて、効果もすぐ実感できます。慣れてきたら、テーブル設計(パーティション・クラスタリング・中間集計)に踏み込んでいくと、コストはさらに安定します。

とはいえ、パーティションの切り方や中間集計テーブルの設計は、データの中身と分析の目的によって最適解が変わります。「自分で運用はできそうだけど、土台の設計だけは間違えたくない」という段階になったら、設計部分は専門家と一緒に固めると、その後の運用がぐっと楽になります。