BigQuery の請求が急に増えたとき|原因のクエリを JOBS ビューで突き止める

BigQuery の料金が急に増えたら、請求のレポートを SKU で分け、INFORMATION_SCHEMA.JOBS の課金量をラベル別・形別・上位順に集計します。検証環境で再現した3つの原因と SQL・結果・直し方を載せました。

この記事をシェア:
目次 クリックで開く

この記事の要点

  1. 請求が増えたら、まず Cloud Billing のレポートを SKU(料金の品目)でまとめ、増えたのがクエリの料金か、保存や容量課金の料金かを分けます。クエリの料金なら、INFORMATION_SCHEMA.JOBS の total_bytes_billed を集計すると、どのクエリかまで分かります。
  2. 当社の検証環境で3つの原因を再現したところ、課金の多い順に上位5本を並べると、1本 10 MB の最低課金が積み上がる小さなクエリ12本は1本しか出ず、合計の大きさは見えませんでした。クエリの形(normalized_literals)でまとめると、処理 約1.5 MiB・課金 120 MiB の1行として出てきます。
  3. 見つけたら、SELECT * は列を選ぶ、日付は絞り込みに対応していない関数で包まずに範囲で絞る、小さな繰り返しは本数を減らす、の順に直します。再発に備えて、課金の上限・ラベル・割り当てを決めておきます。

請求が増えたときに見る順番

請求のレポートSKU でまとめ、クエリの料金かを分ける
ラベル別に足すどの仕組みの分か
課金の多い順重い1本を見つける
形でまとめる軽くて数の多いものを見つける
直す列・日付の絞り込み・本数

探すクエリにも料金がかかる(INFORMATION_SCHEMA への問い合わせは最低 10 MB で、キャッシュされない)

出典:Google Cloud「費用の見積もりと管理」、「JOBS ビュー」、当社の検証

BigQuery の請求が増えたときは、Cloud Billing のレポートで増えた SKU を確かめ、それがオンデマンドのクエリの料金なら、INFORMATION_SCHEMA.JOBS というビューで total_bytes_billed(課金されたバイト数)を集計します。ラベル別、クエリの形別、1本ずつの上位順の3通りで足すと、どの仕組みの、どのクエリが増やしたのかまで説明できます。請求のレポートの SKU から JOBS へ進む順番は、Google Cloud の公式ドキュメントにある「予期しない請求のトラブルシューティング」の手順に沿っています(費用の見積もりと管理)。

当社は BigQuery を中心にしたデータ基盤の設計・構築と、ダッシュボードでの可視化を支援している会社です。この記事では、当社の検証用の Google Cloud プロジェクト(東京リージョン、オンデマンド)に架空の受注明細 600万行を作り、請求を増やす3つの原因をわざと流して、それを4本の SQL で見つけるまでを、実際に流したクエリと結果で載せます。お客様の環境やデータは使っていません。

扱うのは、オンデマンド(読んだデータ量で課金される方式)のクエリの料金です。容量課金(Editions)の予約の料金と、保存の料金は扱いません。料金の全体の構造はデータ統合基盤の費用の記事に、クエリの書き方の最適化はクエリの最適化の記事に書いています。

まず、増えたのがクエリの料金かを請求のレポートで分ける

BigQuery の請求には、クエリの料金のほかに、保存の料金、容量課金の予約やコミットメントの料金、Storage Read API の料金などが入ります。公式の手順は、Cloud Billing のレポートで料金を SKU でグループ化するところから始まります。オンデマンドのクエリの料金は、SKU の一覧では「分析(analysis)」の料金と呼ばれています(BigQuery の料金)。増えた SKU によって、次に開くビューが変わります。

増えた SKU と、次に見るビュー(公式のトラブルシューティングの手順から)
レポートで増えていた SKU次に見るビューこの記事で
オンデマンドの分析(クエリの料金)INFORMATION_SCHEMA.JOBSこのあと全部
予約・コミットメント(容量課金)INFORMATION_SCHEMA.RESERVATIONS、CAPACITY_COMMITMENTS扱わない
保存(ストレージ)INFORMATION_SCHEMA.TABLE_STORAGE(費用の計算には TABLE_STORAGE_USAGE_TIMELINE が勧められている)扱わない

日付の区切りもそろえます。Cloud Billing のレポートの「1日」は、米国太平洋時間の0時で区切られ、米国の夏時間も反映されます(同じページ)。日本時間に直すと、夏時間の間は16時、それ以外は17時が日の境目です。一方、JOBS の creation_time は UTC の時刻で入っています。請求のレポートで「10月14日に増えた」とある日を JOBS で探すなら、日本時間の10月14日16時から15日16時まで(UTC では14日7時から15日7時まで)を絞り込みます。当社の検証では、日本時間の10月14日0時以降を、UTC の13日15時以降として絞りました。

請求先アカウントに複数のプロジェクトがつながっているなら、レポートをプロジェクトでも絞り、どのプロジェクトの分かを決めてから JOBS を開きます。JOBS のビューは、プロジェクトごと・リージョンごとに分かれているからです。

当社の検証環境で、請求を増やす3つの原因を再現した

再現に使ったのは、架空の受注明細 600万行です。注文日は2024年1月から2026年9月まで、店舗は50、表の論理サイズは 873,331,772 バイト(約 0.81 GiB)です。同じ中身を、分割しない表(sales_flat)、注文日の日単位でパーティションを切った表(sales_part)、パーティションに加えて店舗でクラスタにした表(sales_part_clus)の3つで作り、データセット blog_demo に置きました。

そのうえで、3つの形のクエリを流しました。どのジョブにも step というキーのラベルを付け、あとで答え合わせができるようにしています。数字はすべて、2026年10月14日に流したときの実測です。

再現した3つの原因(架空データ、東京リージョン、オンデマンド)
ラベル何を模したか本数処理量(バイト)課金量(バイト)
n1-select-star1店舗のデータだけ欲しいのに、分割しない表に SELECT * … WHERE store_id = 'S010' を流した1873,331,772873,463,808
n1-monthly月次の集計を WHERE FORMAT_DATE('%Y-%m', order_date) = '2026-07' のように、日付を FORMAT_DATE で包んで書いた(定期実行の代わりに7〜9月の3回)3396,000,000396,361,728
n1-dashboardダッシュボードの代わりに、1日分の売上をチャネル別に出す小さな集計を、日付を変えて12回(キャッシュなし)121,622,560125,829,120

3つの原因の課金量

合計 1,331 MiB のうち、62.6%・28.4%・9.0%

SELECT *(1本)833 MiB
日付を FORMAT_DATE で包んだ月次集計(3本)378 MiB
小さな集計の繰り返し(12本)120 MiB

出典:当社の検証用 Google Cloud プロジェクト(東京リージョン、オンデマンド)で、架空の受注明細 600万行を使って 2026年10月14日に実測

16本で課金されたのは、合わせて 1,331 MiB(約 1.30 GiB)です。東京リージョンのオンデマンドの単価は 1 TiB あたり 7.50 米ドルで、毎月最初の 1 TiB は無料です(BigQuery の料金)。無料枠を考えずに単価だけで換算しても約 0.01 米ドルで、表が小さいので金額は小さく出ます。62.6%・28.4%・9.0% という割合も、再現で流す本数(1本・3本・12本)を当社が決めた結果で、本数を変えれば変わります。ここで読むのは金額や割合ではなく、課金のされ方の違いです。

3つは課金のされ方が違います。SELECT * は、1本で表全体を読みました。月次集計は、パーティションのある表なのに、使う列の全期間を読みました。小さな集計は、1本の処理が 13万バイトほどなのに、課金は1本 10 MiB でした。次の節から、この3つを JOBS のビューで見つけていきます。

どのビューを見るか:JOBS、JOBS_BY_USER、JOBS_BY_ORGANIZATION

ジョブの履歴は、INFORMATION_SCHEMA の3つのビューで見られます。違いは、見える範囲と、必要な権限です。

ジョブの履歴を見る3つのビュー
ビュー見える範囲必要なロール(権限)
JOBS(JOBS_BY_PROJECT と同じ)そのプロジェクトで流れた、全員のジョブプロジェクトに対する BigQuery User(roles/bigquery.user)と BigQuery Resource Viewer(roles/bigquery.resourceViewer)。権限は bigquery.jobs.listAll と bigquery.jobs.create
JOBS_BY_USERそのプロジェクトで、自分が流したジョブだけプロジェクトに対する BigQuery User。権限は bigquery.jobs.list
JOBS_BY_ORGANIZATION組織の全プロジェクトのジョブ組織に対する BigQuery Resource Viewer(bigquery.jobs.listAll)。Google Cloud の組織があるときだけ使える

出典は JOBS ビュー、JOBS_BY_USER ビュー、JOBS_BY_ORGANIZATION ビューの各ページです。当社の検証では、ほかの人のジョブが混ざらないよう JOBS_BY_USER を使いました。請求の原因を探すときは、誰が流したクエリか分からないところから始まるので、プロジェクト全体の JOBS を見ます。権限が足りないときは、プロジェクトの管理者に BigQuery Resource Viewer を付けてもらいます。

3つのビューに共通の決まりがあります。

  • リージョンを付けて呼びます(`region-asia-northeast1`.INFORMATION_SCHEMA.JOBS)。クエリを実行する場所とビューのリージョンは一致している必要があります。東京のデータセットを読むクエリは東京で実行されるので、東京のビューで見ます。US など別のリージョンにもデータセットがあるなら、リージョンごとに同じ SQL を流します。
  • 見られるのは、過去180日分の履歴です。組織に入れた、または別の組織へ移したプロジェクトは、移した日より前のジョブが見えません。
  • total_bytes_billed が課金の量を表すのは、オンデマンドのプロジェクトだけです。容量課金のプロジェクトでは参考の値です。行レベルのアクセスポリシーを付けた表を読むジョブは、処理量と課金量が空になります。
  • 複数の文をまとめて流すスクリプトでは、親のジョブ(statement_type = 'SCRIPT')に子のジョブの合計が入ります。費用を合計するときは SCRIPT の行を除きます。

探し方1:ラベルごとに課金量を足す

この節から載せる探し方1〜4の SQL は、4本とも、実際には検証用のラベル(step)で再現したジョブだけに絞る条件を足して流しました。載せた SQL ではその条件を省いています。条件の無いまま流すと、先に流した探すクエリ自身や、同じ時間帯に流れたほかのジョブも結果に入ります。

いちばん早いのは、ジョブに付けたラベルで足すことです。ラベルは labels 列にキーと値の組の配列として入っているので、UNNEST で取り出して GROUP BY します。当社が流した SQL です。

SELECT
  (SELECT value FROM UNNEST(labels) WHERE key = 'step') AS step_label,
  COUNT(*) AS jobs,
  SUM(total_bytes_processed) AS processed,
  SUM(total_bytes_billed) AS billed
FROM `region-asia-northeast1`.INFORMATION_SCHEMA.JOBS_BY_USER
WHERE creation_time >= TIMESTAMP '2026-10-13 15:00:00+00'  -- 日本時間 10月14日 0時
  AND job_type = 'QUERY'
  AND statement_type = 'SELECT'
GROUP BY step_label
ORDER BY billed DESC
探し方1の結果
step_labeljobsprocessedbilled
n1-select-star1873,331,772873,463,808
n1-monthly3396,000,000396,361,728
n1-dashboard121,622,560125,829,120

結果は、流した側で記録した量と1バイトまで同じでした(答え合わせ用に付けたラベルなので、当然の結果です)。実際の環境でも、仕組みごとにラベルが付いていれば、どの仕組みが何本でいくら課金されたかをそのまま説明できます。

弱点は、ラベルを後から付けられないことです。公式は、実行待ち・実行中・完了したジョブのラベルは、追加も変更もできないとしています(リソースへのラベルの追加)。この探し方が使えるのは、前もってラベルを付けて流したジョブだけです。付け方は、bq コマンドなら bq query --label キー:値、API なら jobs.insert の labels、セッションの中なら SET @@query_label = "キー:値" です。キーと値に使えるのは小文字・数字・下線・ハイフンで(日本語などの国際文字も使えます)、ジョブに付けたラベルは請求データにも入ります。

ラベルが無いジョブは、user_email 列(ジョブを流したユーザーのメールアドレスか、サービスアカウント)で足すと、どのアカウントから流れたクエリかが分かります。定期実行やダッシュボードがサービスアカウントで動いているなら、仕組みの当たりはここで付きます。

探し方2:課金の多い順に1本ずつ並べる

ラベルが無くても使えるのが、ジョブを total_bytes_billed の多い順に並べる方法です。クエリの文面は query 列に入っているので、先頭だけ切り出して並べます。

SELECT creation_time, total_bytes_billed, total_bytes_processed, cache_hit,
  LEFT(query, 90) AS query_head
FROM `region-asia-northeast1`.INFORMATION_SCHEMA.JOBS_BY_USER
WHERE creation_time >= TIMESTAMP '2026-10-13 15:00:00+00'  -- 日本時間 10月14日 0時
  AND job_type = 'QUERY'
  AND statement_type = 'SELECT'
ORDER BY total_bytes_billed DESC
LIMIT 5
探し方2の結果(上位5本。2〜4位は同じ量の3本)
順位課金量処理量クエリの先頭(表の名前の前は省略)
1873,463,808873,331,772SELECT * FROM …sales_flat WHERE store_id = 'S010'
2-4132,120,576132,000,000SELECT store_id, SUM(amount) AS sales FROM …sales_part_clus WHERE FO
510,485,760137,520SELECT channel, SUM(amount) AS sales FROM …sales_part WHERE order_da

1位の SELECT * はすぐ分かります。2〜4位には同じ月次集計が3本並び、3本とも同じ量を読んでいます。ところが、小さな集計12本は5位に1本出ただけで、残りの11本は上位5本の外でした。1本ずつの上位は重い1本を見つける方法で、軽いけれど数の多いクエリは、上位の外に散らばって合計が見えません。これを1行にまとめるのが、次の探し方です。

探し方3:同じ形のクエリをまとめる

見落としを拾うのが query_info.query_hashes.normalized_literals です。クエリの文面から、コメント・パラメータの値・UDF・リテラル(日付や店舗コードのような値)を無視して作ったハッシュ値で、値だけが違うクエリは同じ値になります(JOBS ビュー)。これで GROUP BY すると、同じ形のクエリが何本流れ、合わせてどれだけ課金されたかが1行になります。

SELECT
  query_info.query_hashes.normalized_literals AS shape,
  COUNT(*) AS jobs,
  SUM(total_bytes_processed) AS processed,
  SUM(total_bytes_billed) AS billed,
  ANY_VALUE(LEFT(query, 70)) AS example
FROM `region-asia-northeast1`.INFORMATION_SCHEMA.JOBS_BY_USER
WHERE creation_time >= TIMESTAMP '2026-10-13 15:00:00+00'  -- 日本時間 10月14日 0時
  AND job_type = 'QUERY'
  AND statement_type = 'SELECT'
GROUP BY shape
ORDER BY billed DESC
探し方3の結果
shape(先頭8文字)jobsprocessedbilledexample(表の名前の前は省略)
072bf5fe…1873,331,772873,463,808SELECT * FROM …sales_flat …
216da52e…3396,000,000396,361,728SELECT store_id, SUM(amount) AS sales …
1de1f738…121,622,560125,829,120SELECT channel, SUM(amount) AS sales …

日付だけ違う小さな集計12本が1行にまとまり、「12本で、処理は約1.5 MiB なのに課金は 120 MiB」と出てきました。ラベルを使わなくても、探し方1と同じ3つに分かれています。請求の理由を説明するときは、この1行が「同じ形のクエリが12回流れた」という説明の根拠になります。

1本あたりの課金量と本数

1本ずつ並べると小さな集計は目立たないが、形でまとめると12本×10 MiB=120 MiB になる

SELECT *(×1本)833 MiB
月次集計(×3本)126 MiB
小さな集計(×12本)10 MiB

出典:当社の検証用 Google Cloud プロジェクト(東京リージョン、オンデマンド)で、架空の受注明細 600万行を使って 2026年10月14日に実測

注意点が2つあります。ハッシュが入るのは、成功した GoogleSQL のクエリで、キャッシュから返したものではないときだけです。また、SELECT * のように列を暗に指すクエリは、表の列が変わるとハッシュも変わります(同じページ)。期間をまたいで比べるときは、列を足した日の前と後で、同じクエリが別の形に分かれることがあります。

探し方4:最低課金が効いているジョブを数える

小さな集計の課金が処理量の約78倍になったのは、オンデマンドの料金の決まりのためです。料金のページには、課金は MB 単位に切り上げ、クエリが参照する表ごとに最低 10 MB、クエリごとにも最低 10 MB と書かれています。エラーで終わったクエリと、キャッシュから返したクエリは課金されません(BigQuery の料金)。

当社の結果では、課金量はどれも 1,048,576 バイト(1 MiB)の倍数でした。処理が 13万バイト台の小さな集計は、1本 10,485,760 バイト(10 MiB)で課金されています。SELECT * の処理 873,331,772 バイトが 873,463,808 バイト(833 MiB)の課金になったのも、同じ切り上げです。

小さな集計12本の、処理した量と課金された量

1,622,560 バイトの処理に対して、125,829,120 バイトの課金(約78倍)

処理した量(12本の合計)約1.5 MiB
課金された量(12本の合計)120 MiB

出典:当社の検証用 Google Cloud プロジェクト(東京リージョン、オンデマンド)で、架空の受注明細 600万行を使って 2026年10月14日に実測

どれだけのジョブがこの最低課金に当たっているかは、課金量がちょうど 10 MiB で、処理量がそれより少ないジョブを数えると分かります。

SELECT
  COUNTIF(total_bytes_billed = 10485760 AND total_bytes_processed < 10485760) AS min_billed_jobs,
  SUM(IF(total_bytes_billed = 10485760 AND total_bytes_processed < 10485760,
         total_bytes_processed, 0)) AS their_processed,
  SUM(IF(total_bytes_billed = 10485760 AND total_bytes_processed < 10485760,
         total_bytes_billed, 0)) AS their_billed
FROM `region-asia-northeast1`.INFORMATION_SCHEMA.JOBS_BY_USER
WHERE creation_time >= TIMESTAMP '2026-10-13 15:00:00+00'  -- 日本時間 10月14日 0時
  AND job_type = 'QUERY'
  AND statement_type = 'SELECT'
探し方4の結果
min_billed_jobstheir_processedtheir_billed
121,622,560125,829,120

12本すべてが最低課金で、処理の合計 1,622,560 バイトに対し、課金の合計は 125,829,120 バイトでした。金額の目安も書いておきます。1本 10 MiB の最低課金は、約10.5万本で 1 TiB 分(1 TiB ÷ 10 MiB = 104,857.6)になり、東京の単価では 7.50 米ドルです。1か月なら1日あたり約3,500本の計算で、最低課金のクエリが請求を目に見えて動かすのは、この桁の本数が流れているときです。本数がそれより少なければ、金額に効くのは最低課金より、1本で読む量の大きいクエリのほうです。なお、この条件は表を1つだけ読むクエリを数える形です。料金の決まりでは最低課金が表ごとにかかるので、複数の表を結合するクエリは 10 MiB ちょうどにならないことがあります(当社はこの形を試していません)。

探すクエリにも料金がかかる

オンデマンドのプロジェクトでは、INFORMATION_SCHEMA への問い合わせも最低 10 MB の処理として課金され、結果はキャッシュされないので、流すたびに課金されます(INFORMATION_SCHEMA の概要)。当社の4本は、1本ごとに 20,971,520 バイト(20 MiB)、合わせて 80 MiB が課金されました。最低の 10 MB より多かった理由は、公開されている資料では確かめられませんでした。

JOBS の下にある表は creation_time でパーティションに分かれ、project_id と user_email でクラスタになっています。期間を必ず creation_time で絞ると、読む量が減ります。dry run(実行せずに処理量を見積もる機能)の見積もりは、creation_time の絞り込みしか数えないため、実際より大きく出ることがあります(JOBS ビュー)。

見つけた3つを直す:最初にやること

3つとも、直し方は公式のベストプラクティスにあります。当社は同じ表で、直したときの処理量も測っています(2026年10月13〜14日、同じ 600万行の表)。詳しい書き方はクエリの最適化の記事に譲り、ここでは見つけた形ごとに、最初に何を直すかだけ書きます。

SELECT *:列を選ぶ。LIMIT では減らない

オンデマンドの処理量は、読んだ列の量で決まります。同じ sales_flat で、SELECT * … LIMIT 10 の見積もりは表全体の 873,331,772 バイトでした。order_date と amount の2列にすると 96,000,000 バイト(約9分の1)で、これに LIMIT 10 を付けても 96,000,000 バイトのままでした。公式も、クラスタにしていない表では LIMIT で費用は減らないとしています(費用の見積もりと管理)。

WHERE で1店舗に絞っても、分割もクラスタもしていない表では、読む量は減りません。再現の n1-select-star は、WHERE store_id = 'S010' を付けたまま表全体を読みました。中身を眺めたいだけなら、コンソールの表の「プレビュー」は料金がかかりません(同じページ)。

日付は範囲で絞る:対応していない関数で包まず、絞り込みを必須にする

月次集計の3本は、注文日のパーティションがある表なのに、使う3列の全期間(1本 132,000,000 バイト)を読んでいました。WHERE が FORMAT_DATE('%Y-%m', order_date) = '2026-07' のように、パーティションの列を、絞り込みに対応していない関数で包んでいたためです。公式は、パーティションを絞り込むには、比較の片側にパーティションの列だけを置くか、対応している組み込み関数でだけ包むよう書いています。対応している関数として DATE_TRUNC・DATE_ADD・DATE_SUB・EXTRACT(YEAR)・TIMESTAMP_TRUNC などが挙がっていて、FORMAT_DATE はこの一覧にありません。一覧にない関数で包むと、表全体を読むことになります(「Query partitioned tables」の「Isolate the partitioning column or use supported functions」、英語版)。

同じ集計を WHERE order_date BETWEEN '2026-09-01' AND '2026-09-30' と書き直すと、見積もりは 3,934,062 バイトになりました。FORMAT_DATE で包んだときの約34分の1です。この量は最低課金の 10 MB を下回るので、料金の決まりに当てはめると、課金は1本 10 MiB になる計算です。3本で 378 MiB だった課金が、30 MiB になります。

書き方が戻らないように、表に「パーティションの絞り込みを必須にする」設定(require_partition_filter)を付けられます。当社の表で付けると、WHERE の無い集計は、実行の前の見積もりの段階で次のエラーで止まりました(プロジェクト名は伏せています)。

Cannot query over table '<project>.blog_demo.sales_part' without a filter over column(s) 'order_date'
that can be used for partition elimination

WHERE で order_date を範囲で絞ると通りました。この設定で、FORMAT_DATE で包んだ書き方まで止まるかは試していません。

小さな繰り返し:本数を減らすか、同じ文面にしてキャッシュに乗せる

小さな集計12本は、1本の処理を減らしても課金は減りません。最低の 10 MB で課金されているからです。減らせるのは本数です。12日分を1本のクエリで取れば、読むパーティションと列は同じで、最低課金は1回分で済む計算になります(この形は試していません)。

同じクエリを繰り返すなら、キャッシュも効きます。当社の検証では、同じ文面のクエリを2回流すと、2回目はキャッシュから返り、処理量も課金量も 0 でした。ただしキャッシュが使われるのは、文面が空白やコメントまで同じで、参照する表が変わっておらず、CURRENT_DATE() のように実行のたびに値が変わる関数を使っていないとき、などに限られます。結果が残るのは、通常およそ24時間です(キャッシュに保存されているクエリ結果を使用する)。日付だけ違う12本は文面が違うので、キャッシュでは減りません。

直す前と後の処理量(同じ表での見積もり)

dry run の見積もり。範囲で絞った月次集計は、最低課金の 10 MB で課金される計算

SELECT *(全列)873.3 MB
必要な2列だけ96.0 MB
月次集計:FORMAT_DATE で包む132.0 MB
月次集計:日付を範囲で絞る3.9 MB

出典:当社の検証用 Google Cloud プロジェクトで 2026年10月13〜14日に実測(架空の受注明細 600万行、東京リージョン、オンデマンド)

共通の安全装置:課金される最大バイト数

1本のクエリが課金される量に上限をかけるのが「課金される最大バイト数」(maximum bytes billed)です。コンソールではクエリエディタの[展開]→[クエリ設定]→[詳細オプション]、bq コマンドでは --maximum_bytes_billed、API では maximumBytesBilled で指定します(費用の見積もりと管理)。当社の検証で上限を 10 MiB にして SELECT * を流すと、実行前に次のエラーで止まり、課金は 0 でした。

Query exceeded limit for bytes billed: 10485760. 873463808 or higher required.

エラーにある「必要な量」873,463,808 は、再現の SELECT * の課金量と同じ値です。クラスタにした表では見積もりが実際より多めに出るため、実際の課金は上限内でも止まることがある、と公式は書いています。

まだ原因が見えないとき

4つの探し方で請求の増えた分が説明できないときは、次を順に確かめます。当社の検証は1つのプロジェクト・1つのリージョン・自分のジョブだけだったので、ここに書くのは公式の記述から言えることです。

4つの探し方で見えないときに確かめること
見えていない理由の候補確かめること
別のリージョンで流れているデータセットのあるリージョンごとに、`region-us` などへ替えて同じ SQL を流す
別のプロジェクトで流れている請求のレポートをプロジェクトで絞る。組織があれば JOBS_BY_ORGANIZATION で全プロジェクトを見る
SELECT 以外のクエリが重い当社の SQL は statement_type = 'SELECT' に絞っている。実際の調査では statement_type != 'SCRIPT' にして、INSERT・MERGE・CREATE TABLE … AS SELECT なども入れる
クエリの料金ではない請求のレポートの SKU に戻り、保存や容量課金なら別のビューを見る
180日より前に流れたJOBS では見えない。請求データを BigQuery に書き出していれば、そちらで SKU とラベルを見る
請求の日と JOBS の日がずれている太平洋時間の0時(日本時間の16時か17時)で区切り直す

請求書の額と照らし合わせたいときは、JOBS のページにある「オンデマンドのジョブの使用量と請求データを比較する」のサンプルが使えます。日ごとの課金量に単価を掛けて見積もりを出すもので、エラーで終わったジョブは除き、途中で止めたジョブは課金に入れる作りになっています(JOBS ビュー)。

次に増えたとき、すぐ気づけるようにする

ここからは、当社の検証では試していない設定です。公式ドキュメントの記述をもとに、何をするもので、何をしないものかを書きます。

再発に備える設定の役割

気づく

Cloud Billing

  • 予算とアラート:実績か予測がしきい値を超えたらメール
  • 請求データの BigQuery への書き出し:SKU・プロジェクト・ラベルで後から集計

止める

BigQuery

  • 課金される最大バイト数:1本ごとの上限
  • カスタム割り当て:1日あたりの処理量の上限(プロジェクト・ユーザーごと)

分けて数える

クエリを流す仕組み

  • ジョブのラベル:定期実行・ダッシュボード・取り込みなど仕組みごとにキーを決める
  • 後から付けられないので、流す側で付ける

予算アラートは通知だけで、使用も請求も止めない

出典:Cloud Billing「予算と予算アラートの作成、編集、削除」、「Cloud Billing データを BigQuery にエクスポートする」、BigQuery「カスタムのクエリ割り当てを作成する」、「リソースへのラベルの追加」

予算とアラート

Cloud Billing の予算は、決めた予算額に対して、実際の費用か予測の費用が設定した割合を超えたときに、メールで知らせる仕組みです。範囲は請求先アカウント全体のほか、プロジェクト、BigQuery などのサービス、特定のラベルが付いたリソースに絞れます。Pub/Sub に通知を流して、対応を自動にすることもできます。ただし、アラートだけの予算は、使用も請求も自動では止めません(予算と予算アラートの作成、編集、削除)。上限に達するとそのサービスへの新しいリクエストを止める「費用の上限の予算」(spend cap)もありますが、2026年10月に確かめた対象サービスの一覧は Gemini API、Gemini Enterprise Agent Platform(旧 Vertex AI)、Cloud Run、Cloud Run functions で、BigQuery は入っていませんでした(費用の上限の予算を管理する)。

請求データの BigQuery への書き出し

Cloud Billing のデータは、BigQuery のデータセットへ1日を通して自動で書き出せます。標準の使用料金のデータには、SKU・プロジェクト・ラベル・場所・費用などが入り、詳細のデータにはリソース単位の費用も入ります。気をつけたいのは、遡れる範囲がデータセットの場所で変わることです。US か EU のマルチリージョンに置くと、オンにした月の前月の初めまで遡って入ります(再び有効にした場合を除く。最初の遡りには最大5日かかることがあります)。一方、東京などのリージョンに置くと、オンにした日より前は入りません(Cloud Billing データを BigQuery にエクスポートする)。今回のように後から調べる場面に備えるなら、早めにオンにしておくものです。

カスタムの割り当て(1日あたりの処理量の上限)

オンデマンドのクエリには、1日に処理できる量の上限を決められます。[IAM と管理]→[割り当てとシステム上限]で、BigQuery API の「Query usage per day」(プロジェクト全体)と「Query usage per day per user」(ユーザーとサービスアカウントそれぞれ)を TiB で設定します。既定はプロジェクトが1日 200 TiB、ユーザーごとは無制限です。特定のユーザーだけに別の上限を付けることはできません。1日の区切りは太平洋時間の0時で、変更には serviceusage.quotas.update の権限が要り、Quota Administrator(roles/servicemanagement.quotaAdmin)のロールに含まれています(カスタムのクエリ割り当てを作成する)。

上限を超えると、usageQuotaExceeded のエラーでクエリが止まります。ただし公式は、この割り当てはおおよその上限で、超えるクエリが流れることもあると書いています。割り当ての画面の使用量は課金されたバイト数の合計ですが、通常より高い単価で課金されるクエリがあると、JOBS の課金量と一致しないこともあります。

まず、請求が増えた期間のクエリを形でまとめる

請求の理由を聞かれたら、最初に流すのは「探し方3」の、形でまとめる SQL です。自分の環境でそのまま流せるように、ビューをプロジェクト全体の JOBS に替え、ラベルの条件を外し、statement_type を SCRIPT 以外にした形を置きます。読み替えるのは、期間(請求のレポートの1日に合わせる。例は米国が夏時間の間の10月14日)と、リージョンの2か所です。JOBS を見るには BigQuery Resource Viewer のロールが要ります。この形そのものは当社では流していません。

-- 期間:請求のレポートの1日(太平洋時間の0時区切り)に合わせる
-- リージョン:データセットのある場所に替える(例:`region-us`)
SELECT
  query_info.query_hashes.normalized_literals AS shape,
  COUNT(*) AS jobs,
  SUM(total_bytes_billed) AS billed,
  ROUND(SUM(total_bytes_billed) / POW(1024, 4), 4) AS tib_billed,
  ANY_VALUE(user_email) AS example_user,
  ANY_VALUE(LEFT(query, 100)) AS example
FROM `region-asia-northeast1`.INFORMATION_SCHEMA.JOBS
WHERE creation_time >= TIMESTAMP '2026-10-14 07:00:00+00'
  AND creation_time <  TIMESTAMP '2026-10-15 07:00:00+00'
  AND job_type = 'QUERY'
  AND statement_type != 'SCRIPT'
GROUP BY shape
ORDER BY billed DESC
LIMIT 20

どの形のクエリが何本流れ、いくら課金されたかが、課金の多い順に並びます。shape が空の行には、キャッシュから返したジョブや失敗したジョブがまとまります。上の行から、SELECT * か、日付の絞り込みか、本数かを見分けて直していきます。

形ごとの直す順番の決め方や、ラベル・割り当ての決まりを含めた運用の設計は、当社のBigQuery 導入支援で一緒に進められます。

料金と仕様は、2026年10月14日に Google Cloud の公式ドキュメントと料金ページで確かめたものです。デモの数字は、2026年10月13〜14日に当社の検証用プロジェクトで架空のデータを使って測ったものです。料金は変わることがあるので、見積もりの前に公式の料金ページを開いてください。

会計ソフト導入支援

freee・マネーフォワード・勘定奉行・バクラクの選定から、税区分や勘定科目の設計、期首・期中の移行、電子帳簿保存法への対応、周辺システムとの連携までを支援します。

AT
aurant technologies 編集

上場企業からスタートアップまで、数多くのデータ分析基盤構築・AI導入プロジェクトを主導。単なる技術提供にとどまらず、MA/CRM(Salesforce, Hubspot, kintone, LINE)導入によるマーケティング最適化やバックオフィス業務の自動化など、常に「事業数値(売上・利益)」に直結する改善実績多数。

この記事が役に立ったらシェア:

会計ソフトの資料

「会計SaaS移行 完全比較+補助金ガイド」

会計ソフト導入支援の内容を見る