BigQueryのストレージ料金プランを変更して、年間数千万円を節約する

OGP

こんにちは、最近気になるニュースはサザエの学名が数年前に初めて命名されたこと1な、MLデータ部データ基盤ブロックの塩崎です。BigQueryのストレージに関する新料金プランが先日発表されたので、その検証をしました。我々の環境では年間で数千万円という費用削減を達成できることが分かりましたので、BigQueryに多くのデータを蓄積している会社は是非お試しください。

ストレージ費用の悩み

データ基盤を長期間運用していると、データ量の増加が問題になることがしばしばあります。特にユーザーの行動ログやスタースキーマにおけるファクト系テーブルなどはデータがどんどん蓄積されます。古いデータを削除することでデータ量の増加を緩和できますが、それでもサービスの成長に伴いデータ量は増加する傾向になります。

BigQueryはコンピューティングとストレージが高度に分離されているので、初期のAmazon Redshiftのようなストレージを増やすためにCPUも増やす必要はありません。ストレージのみを独立して拡張でき、柔軟なキャパシティプランニングを行えます。これによってある程度は悩みが軽減されましたが、データ量が増えるに従って増加するストレージコストは依然としてデータ基盤運用者の悩みの種になり続けています。先日発表されたBigQueryストレージに関する新料金プランに切り替ることで、データを削減せずにコストを削減できる可能性がでたので、検証しました。

従来の料金プランの紹介

まずは、従来からある料金プランを解説します。これ以降の解説はUS Multi Regionを対象にして行いますので、Tokyo Regionなどの他リージョンでBigQueryを利用されている方は一部の数字が異なります。ご注意ください。

従来の料金プランではlogical storageという「非圧縮」状態のデータ量に応じた費用が発生します。この費用は1GBのデータを1か月保管する毎に0.02USDもしくは0.01USDです。データを格納した直後はActive logical storageというタイプで課金され単価が0.02USDです。また、90日間テーブル・パーティションに対する変更処理を行わなかった場合は、Long-term logical storageに自動的に移行されます。Long-term Storageは単価がActive Storageの半額である0.01USDになります。

cloud.google.com

データ量はデータ型毎に決まり、例えばINT64型ならば8byte、STRING型ならば2byte + UTF-8でエンコードした時のbyte数になります。

cloud.google.com

BigQueryのWeb UIではテーブルを選択した後にDETAILSタブをクリックした後の画面で確認ができます。例えば以下のテーブルでは、Active logical bytesは6.23TB、Long term logical bytesは958.09GBであることが分かります。

Logical Storageに関する情報

それぞれに単価をかけ合わせることで、従来のプランでの費用が分かります。

5.3 * 1024 * 0.02 + 958.09 * 0.01 = 118 USD

新料金プランの紹介

次に新料金プランを解説します。

新料金プランではphysical storageという「圧縮済」状態のデータ量に応じた費用が発生します。この費用は1GBのデータを1か月保管する毎に0.04USDもしくは0.02USDです。データを格納した直後はActive physical storageというタイプで課金され単価が0.04USDです。また、90日間変更をしなかった場合はLong-term physical storageへ移行され0.02USDになります。1GBあたりの単価が従来のものと比べて倍になっていることが分かります。

単価の上昇効果と圧縮によるデータ量の削減効果の両方が作用するので、圧縮によってどの程度データ量が削減されるか次第でどちらが安いのかが変わります。例えば、圧縮でデータ量が1/2になる場合は、データ量削減効果と単価の上昇が打ち消し合い費用は等しくなります。また、圧縮でデータ量が1/10になる場合は圧縮による効果の方が大きいので、新料金プランの方が安価になります。

physical storageに関する情報もlogical storageと同じ画面で確認できます。例えば先程と同じテーブルでは、Active physical bytesは503.19GB、Long term physical bytesは59.89GBであることが分かります。

Physical Storageに関する情報

それぞれに単価をかけ合わせると、新プランでの費用がわかります。

503.19 * 0.04 + 59.89 * 0.02 = 21.3 USD

従来のプランでの費用は118USDでしたので、このテーブルは新プランの方が安価です。

また、新プランの注意点としては、タイムトラベル用のデータに対する費用の発生が挙げられます。クエリを実行するときにテーブル名の後ろに FOR SYSTEM_TIME AS OF <特定の日時> という指定をすると、7日以内であれば任意時点のテーブル情報を取得できます。

cloud.google.com

この機能を実現するためにBigQueryは更新・削除されたデータをTime travel Storageに移動されています。従来のプランはこのTime travel Storageに対する費用がかかりませんが、新プランではActive Storageと同じ1GBあたり0.04USDかかります。そのため、更新・削除を頻繁に行うテーブルはその分の費用がプラスされる点に注意が必要です。なお、BigQueryのWeb UIから確認できるActive physical bytesにはこのTime travel Storageの容量も含まれています。Active physical bytes - Time travel physical byte という計算で、純粋なActive physical Storageの容量を計算できます。

新旧の比較

では、ここで一旦新旧プランの比較を以下の表にまとめます。

従来のプラン 新プラン
Active Storage費用(USD / GB month) 0.02 0.04
Long term Storage費用(USD / GB month) 0.01 0.02
データの圧縮 無し 有り
Time travel Storageに対する費用 無し 有り

どちらが安くなるのかは一概に決められず、保存されているデータの性質に依存します。圧縮率が高く、更新削除を頻繁に行わないデータは新プランに移行した方が安くなる傾向にあることが分かります。

データセット毎の費用比較

ここからはプラン変更でどの程度安くなる・高くなるのかをデータセット毎に集計していきます。

以下のクエリを実行するとデータセット毎に saving_cost が計算されます。この値がプラスの場合は新プランに切り替えたほうが安く、マイナスの場合は従来のプランの方が安いです。クエリを実行するためには、Organizationレベルでの roles/bigquery.metadataViewer ロールが必要です。

with existing_tables as (
  select
    table_catalog as project_id,
    table_schema as dataset_name,
    table_name
  from (
    # 対象のGCPプロジェクトを列挙
    select * from `プロジェクトID1`.`region-us`.INFORMATION_SCHEMA.TABLES union all
    select * from `プロジェクトID2`.`region-us`.INFORMATION_SCHEMA.TABLES union all
    ...
    select * from `プロジェクトIDn`.`region-us`.INFORMATION_SCHEMA.TABLES
  )
), existing_table_storage_by_organization as (
  select
    ts.*
  from `region-us`.INFORMATION_SCHEMA.TABLE_STORAGE_BY_ORGANIZATION as ts
  join existing_tables as et # TABLE_STORAGE_BY_ORGANIZATIONは削除済みテーブルの情報も返すのでTABLESとinner joinする
  on ts.project_id = et.project_id and ts.table_schema = et.dataset_name and ts.table_name = et.table_name
), all_schamata_options as (
  # 対象のGCPプロジェクトを列挙
  select * from `プロジェクトID1`.`region-us`.INFORMATION_SCHEMA.SCHEMATA_OPTIONS union all
  select * from `プロジェクトID2`.`region-us`.INFORMATION_SCHEMA.SCHEMATA_OPTIONS union all
  ...
  select * from `プロジェクトIDn`.`region-us`.INFORMATION_SCHEMA.SCHEMATA_OPTIONS
), datasets as (
  select distinct
    catalog_name as project_id,
    schema_name as dataset_name
  from all_schamata_options
), storage_options_sub as (
  select
    catalog_name as project_id,
    schema_name as dataset_name,
    option_value as storage_billing_model,
  from all_schamata_options
  where option_name = 'storage_billing_model'
), storage_options as (
  select
    d.*,
    # SCHEMATA_OPTIONSはEntity Attribute Valueしているテーブルで、storage_billing_modelが存在しない場合もあるため
    ifnull(so.storage_billing_model, 'LOGICAL') as storage_billing_model
  from datasets as d
  left join storage_options_sub as so
  on d.project_id = so.project_id AND d.dataset_name = so.dataset_name
), cost_by_dataset as (
  select
      project_id,
      dataset_name,
      # US以外のリージョンを対象にする場合は修正
      active_logical_bytes / pow(2, 30) * 0.02 + long_term_logical_bytes / pow(2, 30) * 0.01 as logical_cost,
      active_physical_bytes / pow(2, 30) * 0.04 + long_term_physical_bytes / pow(2, 30) * 0.02 as physical_cost,
      time_travel_physical_bytes / pow(2, 30) * 0.04 as time_travel_cost,
  from (
    select
      project_id,
      table_schema as dataset_name,
      sum(active_logical_bytes) as active_logical_bytes,
      sum(long_term_logical_bytes) as long_term_logical_bytes,
      sum(active_physical_bytes) as active_physical_bytes,
      sum(long_term_physical_bytes) as long_term_physical_bytes,
      sum(time_travel_physical_bytes) as time_travel_physical_bytes,
    from existing_table_storage_by_organization
    group by project_id, table_schema
  ) where long_term_logical_bytes + long_term_logical_bytes + long_term_physical_bytes + long_term_physical_bytes > 1
  order by logical_cost desc
)

select
  c.*,
  c.logical_cost - c.physical_cost as saving_cost,
  s.storage_billing_model,
from cost_by_dataset as c
left join storage_options as s
on c.project_id = s.project_id and c.dataset_name = s.dataset_name

このクエリをZOZOのデータ基盤で実行したところ、ほとんど全てのデータセットでPhysical Storageの方が安いという結果が得られました。また、Logical Storageの方が安いという結果になったデータセットを更に調査したところ、その要因はTime Travel Storageの費用によるものであることも分かりました。純粋なテーブルデータだけの費用ではPhysical Storageの方が安価でした。そのため、後述するTime Travel Windowを調整することで費用削減が可能であることを示唆しています。

Physical Storage課金への切り替え

Physical Storageへの切り替え方法を説明します。

どちらの料金プランを使用するのかはデータセット単位で指定でき、以下のbqコマンドでPhysical Storageによる課金に切り替えることができます。ただし、この作業は一方通行であり、一旦Physical Storageに切り替えたデータセットをLogical Storageに戻すことが不可能な点に注意が必要です。また、Physical Storageによる課金プランは2022年10月27日時点では、まだPreview段階です。そのため、本番環境のデータに対してこのコマンドを実行することには慎重になる必要もあります。

2024-01-02 追記
Phycical Storage機能は2023年7月5日に一般公開(GA)段階に移行しました。また、Physical StorageからLogical Storageに戻すこともできるようになりました。ただし、一旦変更すると14日間は元に戻せないので、依然として切り替えには慎重になる必要があります。
追記ここまで

bq update -d --storage_billing_model=PHYSICAL <プロジェクトID>:<データセット名>

cloud.google.com

Time Travel Windowの調整による費用削減

最後にさらなる費用削減のためにTime Travel Windowを調整する方法を紹介します。

Physical StorageにはTime Travel用のデータも含まれているため、このデータ量を減らすことで費用削減できます。Time Travelで遡ることのできる期間(Time Travel Window)を短くすると、それに伴ってデータ量も減ります。

以下のbqコマンドでTime Travel Windowをデフォルト値である168時間(7日)より短くできます。ただし、Time Travel Windowを短くすると、もしものときにデータ復旧が不可能になるリスクを抱えることになります。費用のみを気にして無闇に小さな値にすることは避けましょう。

bq update -d --max_time_travel_hours=<遡ることのできる時間> <プロジェクトID>:<データセット名>

また、Logical Storage課金のデータセットはTime Travel Storageに対する費用はゼロなので、デフォルト値から変更するメリットはないです。

cloud.google.com

まとめ

BigQueryの新料金プランであるPhysical Storageについて解説をし、切り替えることで費用を削減できるかどうかを確認する方法を紹介しました。データの特性にもよりますが、大きく費用を削減できる可能性のあるプランですので、データ基盤に多くのデータを蓄積している企業は試す価値があるかと思います。

ZOZOでは、一緒に楽しく働く仲間を募集中です。ご興味のある方は下記採用ページをご覧ください!

corp.zozo.com

カテゴリー