BigQuery の請求が急に増えたとき|原因のクエリを JOBS ビューで突き止める
BigQuery の料金が急に増えたら、請求のレポートを SKU で分け、INFORMATION_SCHEMA.JOBS の課金量をラベル別・形別・上位順に集計します。検証環境で再現した3つの原因と SQL・結果・直し方を載せました。
目次 クリックで開く
この記事の要点
- 請求が増えたら、まず Cloud Billing のレポートを SKU(料金の品目)でまとめ、増えたのがクエリの料金か、保存や容量課金の料金かを分けます。クエリの料金なら、INFORMATION_SCHEMA.JOBS の total_bytes_billed を集計すると、どのクエリかまで分かります。
- 当社の検証環境で3つの原因を再現したところ、課金の多い順に上位5本を並べると、1本 10 MB の最低課金が積み上がる小さなクエリ12本は1本しか出ず、合計の大きさは見えませんでした。クエリの形(normalized_literals)でまとめると、処理 約1.5 MiB・課金 120 MiB の1行として出てきます。
- 見つけたら、SELECT * は列を選ぶ、日付は絞り込みに対応していない関数で包まずに範囲で絞る、小さな繰り返しは本数を減らす、の順に直します。再発に備えて、課金の上限・ラベル・割り当てを決めておきます。
請求が増えたときに見る順番
探すクエリにも料金がかかる(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 | 次に見るビュー | この記事で |
|---|---|---|
| オンデマンドの分析(クエリの料金) | 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日に流したときの実測です。
| ラベル | 何を模したか | 本数 | 処理量(バイト) | 課金量(バイト) |
|---|---|---|---|---|
| n1-select-star | 1店舗のデータだけ欲しいのに、分割しない表に SELECT * … WHERE store_id = 'S010' を流した | 1 | 873,331,772 | 873,463,808 |
| n1-monthly | 月次の集計を WHERE FORMAT_DATE('%Y-%m', order_date) = '2026-07' のように、日付を FORMAT_DATE で包んで書いた(定期実行の代わりに7〜9月の3回) | 3 | 396,000,000 | 396,361,728 |
| n1-dashboard | ダッシュボードの代わりに、1日分の売上をチャネル別に出す小さな集計を、日付を変えて12回(キャッシュなし) | 12 | 1,622,560 | 125,829,120 |
3つの原因の課金量
合計 1,331 MiB のうち、62.6%・28.4%・9.0%
出典:当社の検証用 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つのビューで見られます。違いは、見える範囲と、必要な権限です。
| ビュー | 見える範囲 | 必要なロール(権限) |
|---|---|---|
| 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
| step_label | jobs | processed | billed |
|---|---|---|---|
| n1-select-star | 1 | 873,331,772 | 873,463,808 |
| n1-monthly | 3 | 396,000,000 | 396,361,728 |
| n1-dashboard | 12 | 1,622,560 | 125,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
| 順位 | 課金量 | 処理量 | クエリの先頭(表の名前の前は省略) |
|---|---|---|---|
| 1 | 873,463,808 | 873,331,772 | SELECT * FROM …sales_flat WHERE store_id = 'S010' |
| 2-4 | 132,120,576 | 132,000,000 | SELECT store_id, SUM(amount) AS sales FROM …sales_part_clus WHERE FO |
| 5 | 10,485,760 | 137,520 | SELECT 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
| shape(先頭8文字) | jobs | processed | billed | example(表の名前の前は省略) |
|---|---|---|---|---|
| 072bf5fe… | 1 | 873,331,772 | 873,463,808 | SELECT * FROM …sales_flat … |
| 216da52e… | 3 | 396,000,000 | 396,361,728 | SELECT store_id, SUM(amount) AS sales … |
| 1de1f738… | 12 | 1,622,560 | 125,829,120 | SELECT channel, SUM(amount) AS sales … |
日付だけ違う小さな集計12本が1行にまとまり、「12本で、処理は約1.5 MiB なのに課金は 120 MiB」と出てきました。ラベルを使わなくても、探し方1と同じ3つに分かれています。請求の理由を説明するときは、この1行が「同じ形のクエリが12回流れた」という説明の根拠になります。
1本あたりの課金量と本数
1本ずつ並べると小さな集計は目立たないが、形でまとめると12本×10 MiB=120 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倍)
出典:当社の検証用 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'
| min_billed_jobs | their_processed | their_billed |
|---|---|---|
| 12 | 1,622,560 | 125,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 で課金される計算
出典:当社の検証用 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つのリージョン・自分のジョブだけだったので、ここに書くのは公式の記述から言えることです。
| 見えていない理由の候補 | 確かめること |
|---|---|
| 別のリージョンで流れている | データセットのあるリージョンごとに、`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・マネーフォワード・勘定奉行・バクラクの選定から、税区分や勘定科目の設計、期首・期中の移行、電子帳簿保存法への対応、周辺システムとの連携までを支援します。
