はじめに
この記事は ラクスパートナーズ Advent Calendar 2025 の12日目の記事です。
直近担当した「BigQueryのコスト削減」業務の中で実施した内容についてまとめていこうと思います。
とは言っても具体的な数字などは記載できないため、「ほ〜こういうことをすればコスト削減につながるんだ〜」くらいの温度感で読んでもらえればと思います!
コスト構造の理解
具体的な施策の話に入る前に、まずBigQueryの課金体系をおさらいします。
BigQueryのコストは、大きくストレージ料金 とコンピューティング料金 の2つに分けることができます。
これらは独立して計算されるため、コスト削減のアプローチもそれぞれ別々に考える必要があります。
ストレージ料金
データをBigQuery上に保持するための料金です。
単純なデータ量だけでなく、データの「状態」や「課金モデル」によって単価が変わるのがポイントです。
コンピューティング料金
SQLクエリを実行してデータを分析する際にかかる料金です。
料金体系としては、データスキャン量に応じて課金される 「オンデマンド料金モデル」
使用したコンピュートリソース(スロット)量に応じて課金される 「BigQuery Editions」
の2つが存在します。
今回はストレージ編と題して、ストレージ料金の削減のために検討・実施した施策について紹介していきます。
ストレージ課金モデルの見直し
BigQueryのストレージ料金には、データセット単位で選択可能な2つの課金モデルが存在します。
論理課金モデル
データの 「圧縮前のバイト数」 に基づいて課金されます。デフォルトではこちらの設定が適用されます。
物理課金モデル
データがディスクに保存される際の 「圧縮後のバイト数」 に基づいて課金されます。
BigQueryは内部的に高い圧縮率でデータを保存するため、圧縮が効きやすいデータの場合、論理課金よりも大幅に安くなるケースがあります。
以下は、各課金モデルにおける料金の比較です。
| ストレージの種類 | 料金 (月額) |
|---|---|
| アクティブな論理ストレージ | $0.023 / 1 GiB |
| 長期の論理ストレージ | $0.016 / 1 GiB |
| アクティブな物理ストレージ | $0.052 / 1 GiB |
| 長期の物理ストレージ | $0.026 / 1 GiB |
ご覧いただいている通り、おおよそデータの圧縮率が2.5倍以上のテーブルについては、物理課金モデルの方が安くなることが分かります。
また、Google公式が提供している以下のクエリを実行することで、「今後30日間において各課金モデルでどれだけのコストがかかるか」の予測値を算出できます。
(実行するには INFORMATION_SCHEMA に対する読み取り権限が必要です!)
DECLARE active_logical_gib_price FLOAT64 DEFAULT 0.02;
DECLARE long_term_logical_gib_price FLOAT64 DEFAULT 0.01;
DECLARE active_physical_gib_price FLOAT64 DEFAULT 0.04;
DECLARE long_term_physical_gib_price FLOAT64 DEFAULT 0.02;
WITH
storage_sizes AS (
SELECT
table_schema AS dataset_name,
-- Logical
SUM(IF(deleted=false, active_logical_bytes, 0)) / power(1024, 3) AS active_logical_gib,
SUM(IF(deleted=false, long_term_logical_bytes, 0)) / power(1024, 3) AS long_term_logical_gib,
-- Physical
SUM(active_physical_bytes) / power(1024, 3) AS active_physical_gib,
SUM(active_physical_bytes - time_travel_physical_bytes) / power(1024, 3) AS active_no_tt_physical_gib,
SUM(long_term_physical_bytes) / power(1024, 3) AS long_term_physical_gib,
-- Restorable previously deleted physical
SUM(time_travel_physical_bytes) / power(1024, 3) AS time_travel_physical_gib,
SUM(fail_safe_physical_bytes) / power(1024, 3) AS fail_safe_physical_gib,
FROM
`region-REGION`.INFORMATION_SCHEMA.TABLE_STORAGE_BY_PROJECT
WHERE total_physical_bytes + fail_safe_physical_bytes > 0
-- Base the forecast on base tables only for highest precision results
AND table_type = 'BASE TABLE'
GROUP BY 1
)
SELECT
dataset_name,
-- Logical
ROUND(active_logical_gib, 2) AS active_logical_gib,
ROUND(long_term_logical_gib, 2) AS long_term_logical_gib,
-- Physical
ROUND(active_physical_gib, 2) AS active_physical_gib,
ROUND(long_term_physical_gib, 2) AS long_term_physical_gib,
ROUND(time_travel_physical_gib, 2) AS time_travel_physical_gib,
ROUND(fail_safe_physical_gib, 2) AS fail_safe_physical_gib,
-- Compression ratio
ROUND(SAFE_DIVIDE(active_logical_gib, active_no_tt_physical_gib), 2) AS active_compression_ratio,
ROUND(SAFE_DIVIDE(long_term_logical_gib, long_term_physical_gib), 2) AS long_term_compression_ratio,
-- Forecast costs logical
ROUND(active_logical_gib * active_logical_gib_price, 2) AS forecast_active_logical_cost,
ROUND(long_term_logical_gib * long_term_logical_gib_price, 2) AS forecast_long_term_logical_cost,
-- Forecast costs physical
ROUND((active_no_tt_physical_gib + time_travel_physical_gib + fail_safe_physical_gib) * active_physical_gib_price, 2) AS forecast_active_physical_cost,
ROUND(long_term_physical_gib * long_term_physical_gib_price, 2) AS forecast_long_term_physical_cost,
-- Forecast costs total
ROUND(((active_logical_gib * active_logical_gib_price) + (long_term_logical_gib * long_term_logical_gib_price)) -
(((active_no_tt_physical_gib + time_travel_physical_gib + fail_safe_physical_gib) * active_physical_gib_price) + (long_term_physical_gib * long_term_physical_gib_price)), 2) AS forecast_total_cost_difference
FROM
storage_sizes
ORDER BY
(forecast_active_logical_cost + forecast_active_physical_cost) DESC;
実行結果からどちらの課金モデルがより適しているのかを把握することができます。
私が所属しているチームではBigQueryのデータセットをTerraformで管理しているため、
- 上記クエリを実行
- 設定変更対象のデータセットを抽出
- Terraformで一括設定変更
という流れを踏むだけで、かなりのコスト削減を実現できました。
- 設定を変更してから実際に課金体系が切り替わるまでには 24時間 かかります
- 一度変更を行うと、その後 14日間 は再変更することができません
タイムトラベル期間の短縮
物理課金モデルへの移行とセットで検討すべきなのが 「タイムトラベル期間」 の設定です。
タイムトラベルとは
テーブルのデータを誤って削除・更新してしまった際に、過去の状態に遡ってデータを復元できる「フェイルセーフ機能」です。
デフォルトでは過去 7日間 まで遡ることができ、設定により 2日〜7日 の間で調整可能です。
なぜ「物理課金」だと設定見直しが有効なのか
実は、この機能にかかるコストはどの課金モデルを適用しているかによって異なります。
-
論理課金の場合
タイムトラベル用の履歴データは 無料 です。 -
物理課金の場合
タイムトラベル用の履歴データも 物理ストレージ使用量として課金対象 になります。
つまり、物理課金モデルを選択した場合、タイムトラベル期間を長く設定すればするほど、裏側で保持される履歴データの分だけストレージ料金が加算されてしまいます。
今回の取り組みでは、物理課金モデルへ移行したデータセットに対し、タイムトラベル期間をデフォルトの7日から短縮することで更なるコスト削減を行いました。
もちろん、期間を短縮することでリカバリの猶予期間も短くなるため、開発運用上のリスク許容度とコストのバランスを考慮して決定する必要があります。
テーブルの自動削除設定の追加
ストレージコスト削減において「既存データをどう安く管理するか」と同様に重要になってくるのが 「そもそも不要なデータを置かない」 という点です。
開発環境では以下のような「一時的なテーブル」が日常的に作成されています。
-
本番環境への実装前に検証用として作成したテーブル
-
ちょっとした集計やアドホックな分析のために作成した中間テーブル
本来であれば、開発や分析が終わったタイミングで削除されるべきですが、
日常の業務に追われていると、こういった運用を徹底してもらうことはなかなかに困難であると言えます。
データセットレベルでテーブルの有効期限を設定する
BigQueryにはデータセット配下に作成されるテーブルに対して、デフォルトの有効期限 を設定する機能があります。
これを開発環境のデータセットに適用することで不要となったテーブルの削除漏れを防ぐことができます。
なお、この設定はあくまでデフォルト値として機能するため、「これは残しておきたい!」というテーブルだけ有効期限を解除するといった、テーブル単位での柔軟な運用も可能です。
サンプルコード
今回ご紹介したコスト削減施策の設定を、Terraformで実装する場合のサンプルコードを載せておきます。
resource "google_bigquery_dataset" "sample_dataset" {
dataset_id = "sample-dataset"
location = "asia-northeast1"
description = "サンプルコード"
storage_billing_model = "PHYSICAL" # 物理課金モデル
max_time_travel_hours = 48 # タイムトラベル期間2日間
default_table_expiration_ms = 86400000 # テーブルは作成後24時間で削除
}
まとめと今後の展望
今回は、BigQueryのコスト削減施策のうち、主にストレージ設定や運用ルールの見直しについて紹介しました。
個人的にコスト削減の取り組みを通して得られた最大の収穫は、
「どこにどう課金されてるのか」「どうすれば安くなるのか」を突き詰めて調査することで、
BigQueryの裏側の仕組みやアーキテクチャに対する理解を深めることができたことです。
適切なコスト管理は、技術的な仕様理解の上に成り立つものなんだなと改めて感じました。
さて、コストのもう一つの大きな柱である「コンピューティング料金」については、現在 BigQuery Editions の導入検証を絶賛実施中です。
スロットのAutoscaling挙動の確認や、オンデマンド課金との詳細な比較など、検証すべき項目は多岐にわたります。
こちらについても、ある程度知見が溜まり体系的に整理できたら、改めて別の記事としてまとめたいなと考えています!!
▼ BigQueryの仕組みを理解する上で、非常に参考になった書籍