BigQueryのストレージ費用を78%削減!論理課金と物理課金の見極め方
私たちのチームでは、朝日放送グループ全体の統合データ基盤として Google Cloud の BigQuery を利用しています。ありがたいことに扱うデータは年々増えているのですが、それに伴いコストにも真剣に向き合う必要が出てきました。
BigQuery のコストというと、まず気にするのは「クエリのスキャン量」かと思います。一方で、ストレージ費用の方は「溜まる分だけ増えるのは仕方ない」と、あまり深く見ていない方も多いのではないでしょうか。
実は、BigQuery にはストレージ費用の課金モデルとして、論理課金・物理課金の2つがあることは、前から知っていました。ただ、正直そこまで違いがあるとは思っていなかったのと、データ基盤が年々巨大になるにつれ「どのデータセットを切り替えるべきか」を1つずつ調べるのも大変で、ずっと後回しにしていました。ところが今回、その調査を AI エージェントに任せてみたところ、驚くほどあっさり見極めることができました。
今回は、このストレージ課金モデル (論理課金 / 物理課金) を見直して、最もストレージ費用の大きかったプロジェクトで 78% 以上削減した話と、その見極め方をご紹介します。
そもそもストレージ課金モデルとは?
BigQuery のストレージ課金には、2つのモデルがあります。
- 論理(LOGICAL)課金: 非圧縮のバイト数で課金。何も設定しなければこちら(デフォルト)
- 物理(PHYSICAL)課金: 圧縮後のバイト数で課金
単価は東京リージョン(asia-northeast1)でおおよそ以下の通りです(2026年7月時点の実請求ベース、USD/GiB/月)。
| 論理課金 | 物理課金 | |
|---|---|---|
| Active storage(90日以内に更新あり) | $0.023 | $0.052 |
| Long-term storage(90日間更新なし) | $0.016 | $0.026 |
※最新の単価は公式ドキュメントのストレージ料金を参照してください。課金モデルの仕様はデータセットの概要にまとまっています。
ここで押さえておきたいのは、どちらのモデルでも、実際に保存されているデータは同じだということです。BigQuery は課金モデルに関係なく、常にデータを圧縮した物理バイトで保存しています。変わるのは「請求書の計算にどちらのサイズを使うか」だけです。
そして公称単価では、物理課金は論理課金の約2倍に設定されています(上の実単価でも1.6〜2.3倍です)。つまり、データが2倍以上圧縮されるなら、物理課金に切り替えた方が安くなる、というのが基本の損益分岐です。BigQuery は列指向で圧縮が効きやすく、特にログのような繰り返しの多いデータでは圧縮率が数十倍に達することもあります(実際、後述する私たちのログ系データセットでは30倍超でした)。
そうなってくると、「じゃあ全部物理課金にすればいいのでは?」となってしまいます。しかし、そう簡単にはいかないため、次はそちらを解説したいと思います。
「物理課金が必ず安い」わけではない
物理課金には、単価が高いこと以外にもう1つ大きな違いがあります。それは、タイムトラベルとフェイルセーフのストレージにも課金されることです。
そもそもタイムトラベル / フェイルセーフとは?
タイムトラベルとは、テーブルを誤って更新・削除してしまっても、過去7日以内(既定)の状態に復元できる BigQuery の機能です。さらにその後ろには、緊急復旧用に7日間データを保持するフェイルセーフという領域もあります(こちらは期間の短縮ができません)。
この2つの機能は、裏側では「更新や削除で置き換わった旧バージョンのデータ」を保持することで実現されています。そして、
- 論理課金: 旧バージョンの保持分は課金対象外(基本料金に含まれる)
- 物理課金: 旧バージョンの保持分も課金対象
という違いがあります。
つまり、1日1回テーブルを丸ごと作り直す運用では、置き換わった旧バージョンが毎日1つずつ積み重なっていき、タイムトラベル7日分+フェイルセーフ7日分=約14日分、実データにして十数倍もの旧バージョンが常に裏側にたまった状態になります。1日に何度も上書きするテーブルなら、これがさらに何倍にも膨らみます。こうなると、いくら圧縮が効いていても物理課金の方が高くつきます。
実際、後述する棚卸しでは、私たちの環境からこんな極端な例が見つかりました。1日に何十回も全件洗い替えしている、あるデータセットです。
- 実データはごく小さく、論理課金での保管料はほぼ無視できる規模
- 一方、旧バージョンの保持分(タイムトラベル + フェイルセーフ)は実データの300倍以上
- 物理課金に切り替えると、このデータセットの保管料は数百倍に跳ね上がる計算(!)😳
「圧縮率2倍超なら物理が安い」の目安だけで一括切替をしていたら、大変なことになっていました。データセットごとに「圧縮の効き具合」と「更新のされ方」を見て個別に判断する必要があるわけです。
まず現状を測ってみる
判断に必要な数字は、こちらの INFORMATION_SCHEMA.TABLE_STORAGE_BY_PROJECT ビューにすべて入っています。データセットごとに、圧縮の効き具合(compression_ratio)と、物理課金にした場合の費用が論理課金の何倍になるか(physical_cost_ratio)を並べるクエリがこちらです。physical_cost_ratio が 1 未満なら物理課金が安い(切り替え候補)、1 を大きく超えるなら物理課金の方が高くつく(切り替え NG)と読めます。
なお、この課金モデルはテーブル単位ではなくデータセット単位でしか設定できません。そのため上のクエリもデータセット単位で集計しており、1つのデータセットに性質の異なるテーブルが混在している場合は、まとめてどちらか一方に寄せるしかありません。個々のテーブルを最適化するのではなく、データセット全体での総合的な判断が必要になるわけです。
-- 単価は asia-northeast1 のもの(実際の請求単価は請求データでの確認を推奨)
DECLARE active_logical_price FLOAT64 DEFAULT 0.023;
DECLARE long_term_logical_price FLOAT64 DEFAULT 0.016;
DECLARE active_physical_price FLOAT64 DEFAULT 0.052;
DECLARE long_term_physical_price FLOAT64 DEFAULT 0.026;
WITH storage AS (
SELECT
table_schema AS dataset_name,
-- 論理(非圧縮)バイト
SUM(IF(deleted = FALSE, active_logical_bytes, 0)) / POW(1024, 3) AS active_logical_gib,
SUM(IF(deleted = FALSE, long_term_logical_bytes, 0)) / POW(1024, 3) AS long_term_logical_gib,
-- 物理(圧縮後)バイト ※active_physical_bytes にはタイムトラベル分も含まれる
SUM(active_physical_bytes) / POW(1024, 3) AS active_physical_gib,
SUM(long_term_physical_bytes) / POW(1024, 3) AS long_term_physical_gib,
SUM(time_travel_physical_bytes) / POW(1024, 3) AS time_travel_physical_gib,
SUM(fail_safe_physical_bytes) / POW(1024, 3) AS fail_safe_physical_gib
FROM `region-asia-northeast1`.INFORMATION_SCHEMA.TABLE_STORAGE_BY_PROJECT
WHERE table_type = 'BASE TABLE'
GROUP BY dataset_name
),
costs AS (
SELECT
dataset_name,
active_logical_gib * active_logical_price + long_term_logical_gib * long_term_logical_price AS logical_cost,
(active_physical_gib + fail_safe_physical_gib) * active_physical_price + long_term_physical_gib * long_term_physical_price AS physical_cost,
SAFE_DIVIDE(active_logical_gib + long_term_logical_gib, active_physical_gib - time_travel_physical_gib + long_term_physical_gib) AS compression_ratio
FROM storage
),
ranked AS (
SELECT
dataset_name,
ROUND(compression_ratio, 1) AS compression_ratio,
-- 物理課金にした場合の費用が論理課金の何倍か(1未満なら物理が安い=切替候補)
ROUND(SAFE_DIVIDE(physical_cost, logical_cost), 2) AS physical_cost_ratio,
ROW_NUMBER() OVER (ORDER BY logical_cost - physical_cost DESC) AS rn_top,
ROW_NUMBER() OVER (ORDER BY logical_cost - physical_cost ASC) AS rn_bottom
FROM costs
)
-- データセットが多いので、削減額の大きい順に上位6件と下位4件だけを抜き出す
SELECT dataset_name, compression_ratio, physical_cost_ratio
FROM ranked
WHERE rn_top <= 6 OR rn_bottom <= 4
ORDER BY rn_top;
実行結果:

▲ 実行結果の抜粋。上位6件=物理課金が有利な切り替え候補(
physical_cost_ratioが 1 未満)、下位4件=物理課金にすると費用が数倍〜数百倍以上に膨らむデータセット。
結果は想像以上でした。上位に来たのは、日々の行動ログをひたすら溜め込んでいる2つのデータセットで、圧縮率にして約37倍と約73倍。物理課金にすれば費用は論理課金の約25分の1(physical_cost_ratio ≒ 0.04)で、元々大きなデータセットということもあり、この2つだけでプロジェクトのストレージ費用の大半を削減できる計算でした。
※なお、このクエリはあくまで切り替え候補を洗い出すための概算です。単価は固定値かつその時点のスナップショットなので、最終的な効果は後述のとおり実際の請求データで確認します。
この2つは「毎日追記されるだけで、過去分はほぼ書き換わらない」タイプのデータなので、旧バージョンの保持分もごくわずか。まさに物理課金向きの性質です。
その一方で、先ほどの「切り替えたら数百倍」のようなデータセットも、同じ環境には埋まっています。この表の数字だけを見て機械的に切り替えるわけにはいきません。
190個のデータセットの「更新パターン」調査は AI エージェントに任せる
ここで問題になるのが調査のボリュームです。私たちの環境には約190のデータセットがあり、切替候補それぞれについて「テーブルがどう作られているか(全件再構築か、差分の追記か)」「どのくらいの頻度で更新されているか」を確認する必要があります。これまでの私であれば、データセットを1つずつ開き、パイプラインの定義(私たちの場合は Dataform と Airflow)を確認し、スプレッドシートに転記して…という作業を数日がかりでやっていたと思います(想像するだけで気が遠くなりますね😵💫)。
今回はこの調査を、AI エージェント(Claude Code)にほぼ丸ごと任せてみました。具体的には、切替候補のデータセットごとにエージェントを並列で走らせ、
- リポジトリから各テーブルの定義(Dataform の
type: table/incremental、DAG のロード方式・実行頻度)を読み取って、更新パターンを分類する - 「このデータセットは、1日にどれだけ書き換わったら損益が逆転するか」というストレステストを全候補で計算する(現状の何倍まで更新が増えても大丈夫か、という安全マージンの算出です)
- 日を変えて取得したスナップショットで、判定が反転しないことを確認する
といったことをやってもらいました。人手なら数日がかりだったはずの調査が半日足らずで終わり、最終的に「17データセットは切替推奨、更新が激しすぎる1つは見送り、削減額が小さい多数は保留」という判定リストが得られました。
数字の集計だけなら SQL でもできますが、「このテーブルは毎時全件再構築だから危ない」といった実装まで踏み込んだ判断を全データセットに対して行えたのは、エージェントに調査を委ねられたからこそだと感じています。
Terraform で切り替える
課金モデルはデータセット単位の設定で、私たちはデータセットを Terraform で管理しているため、切り替えは以下の1行を追加するだけです。
resource "google_bigquery_dataset" "abc_log" {
dataset_id = "abc_log"
location = "asia-northeast1"
storage_billing_model = "PHYSICAL" # ← これを追加するだけ
}
冒頭で触れた通り、保存されている実体はどちらのモデルでも同じなので、切り替えにあたってデータの洗い替えや再圧縮は一切不要です。ダウンタイムもなく、クエリへの影響もありません。Long-term storage の「90日間更新なし」のカウンタもリセットされません(詳細は公式の課金モデルの更新手順も参照してください)。
⚠️ 注意点:14日間のロック
ただし、「試しに切り替えて、ダメならすぐ戻す」はできません。変更の反映には約24時間かかるうえ、一度変更した課金モデルは14日間ロックされるためです。切り替え前の見極めが大切になる理由がここにあり、先ほどのストレステストで安全マージンを確認していたのも、この制約があるからです。
実際の請求で効果を確認する
まずは効果が最も大きく、かつ追記中心で安全な先ほどのログ系2データセットだけを切り替えました。数日後、請求データ(Cloud Billing の請求エクスポート)で、切り替えたデータセットを抱えるプロジェクトの BigQuery ストレージ費用の日額を確認してみると、、、
| 日付 | ストレージ費用の日額(切替前 = 100) |
|---|---|
| 7/2(切替前) | 100 |
| 7/3(移行中) | 30 |
| 7/4(切替後) | 22 |
| 7/5 | 22 |

切替前に比べて費用は22%程度まで低下、削減率にして 約78% です。たった2つのデータセットに1行ずつ追加しただけの変更としては、十分すぎる効果ではないでしょうか。
(その後、さらに変更を加えて、もう少し削減できました。)
一度の最適化で終わらせないために
ところで、この「論理か物理か」の見極めがやっかいなのは、新しく作るデータセットでは事前に判断できないことです。圧縮率も更新パターンも、実データが溜まって初めて測れるものだからです。
そこで私たちは、「作成時に正解を当てる」ことは最初から諦めて、次のような運用に落ち着きました。
- 新規データセットは、更新が増えても課金が跳ねない安全な論理課金でスタートする
- 月次で全データセットの論理/物理コストを再計算し、現行の課金モデルと乖離が出たものを Slack に通知する監視処理(Airflow DAG)を用意する
監視は「論理のまま放置された削減余地」と「物理化後に更新が増えて逆転した」の両方向を検知します。一括見直しは今回一度きりにして、あとは通知が来たときだけ判断すればよい体制です。この監視 DAG の実装も AI エージェントと一緒に進めました(新規データセットに課金モデルの明示を求める軽い CI チェックも添えてあります)。
まとめ
最後に、今回の見直しで押さえておきたいポイントを整理します。
- BigQuery のストレージ課金には論理(非圧縮)と物理(圧縮後)の2モデルがあり、デフォルトは論理課金。単価は物理が約2倍のため、圧縮率2倍超が損益分岐の目安
- ただし物理課金はタイムトラベル / フェイルセーフの旧バージョン保持分にも課金されるため、更新の激しいデータセットではむしろ高くつく
- 判断材料は
INFORMATION_SCHEMA.TABLE_STORAGE_BY_PROJECTにすべて揃っている - 切替は無停止・洗替不要だが、変更後14日間はロックされる点に注意
データセットごとの目安としては、以下のように考えるとよいかと思います。
- 追記中心のログ・履歴系で圧縮がよく効く → 物理課金
- 全件再構築や大量更新が高頻度で走る → 論理課金のまま
- 迷う・新規で実績がない → 論理課金で開始して、実測してから判断
ストレージ費用は、何もしなくても毎月確実に発生し続けるコストです。だからこそ一度の見直しの効果が毎月効き続けますし、これまでなら着手をためらった「全データセットの棚卸し」も、AI エージェントに調査を任せられるようになったことで、ぐっと現実的になりました。
同じように「ストレージ費用は仕方ないもの」と思っている方に、この記事が少しでも参考になれば幸いです。こうした地道なコスト最適化も、AI の力を借りながら継続的に取り組んでいきたいですね。
AUTHOR

朝日放送グループホールディングス株式会社 デジタル・アーキテック局 データ戦略チーム
グループ全体の統合的なデータ基盤の構築・データ分析の支援に従事している。 動画配信・テレビの視聴データ分析等で身につけた幅広い知識を活かして日々奮闘中!




