はじめに:GA4データの保持と保全
GA4のBigQueryエクスポートを運用していると、いつかは「古いデータをどうするか」という判断に直面します。events_YYYYMMDD というテーブルは毎日ひとつずつ積み上がっていくので、何年も放置すればストレージ費用がじわじわ膨らみますし、テーブルの数が増えすぎてデータセットが見通しの悪い状態になります。
この問題への対処として、テーブル有効期限やパーティション有効期限を使って古いデータを自動削除する方法があります。ただ、ここで気をつけたいのは「削除は元に戻せない」という点です。一度消えたGA4の生データは、原則として二度と手に入りません。GA4の画面上の集計レポートは残っても、BigQueryに流れていた1行単位のイベントデータは復元できないのです。
そこで現実的な落としどころになるのが、「BigQueryからは消すけれど、安いCloud Storage(GCS)には退避しておく」という運用です。日常の分析で使う直近データはBigQueryに置き、それより古いものはファイルとしてGCSに保管します。そして「あの時期のデータをもう一度見たい」という場面が来たら、そのときだけBigQueryに戻す。こうすれば、保存コストを抑えつつ、いざというときの保全性も確保できます。
この記事では、GA4データをGCSへ退避し、必要なときにBigQueryへ再インポートするまでの一連の流れを整理します。なお、各コマンドのオプションや機能の細かい仕様は更新が早い領域なので、実際に作業するときは必ず最新の公式ドキュメントで確認してください。ここでは「どんな道具を、どう組み合わせるか」という考え方を中心にお伝えします。
データの自動削除そのものについては、こちらの記事もあわせて読んでみてください。
なぜGCSに退避するのか
BigQueryのストレージとCloud Storageでは、データを置いておくときの考え方が少し違います。BigQueryは分析のために「すぐクエリで叩ける」状態を保つストレージで、それなりの単価がかかります。一方GCSは、アクセス頻度の低いデータ向けに割安な保存クラスが用意されていて、めったに読まないデータを長く寝かせておくのに向いています。
つまり、「毎日のように分析する直近データ」と「年に数回見返すかどうかの古いデータ」を同じ場所に置いておくのは、コストの面で少しもったいない状態です。古いものをGCSへ移しておけば、保存コストを抑えながら、データそのものは失わずに済みます。
退避するときのファイル形式は、後で戻すことを考えるとAvroかParquetがおすすめです。これらはスキーマ(列名や型の情報)をファイル自身が持っているため、再インポートのときにスキーマを手作業で組み立て直す手間が大きく減ります。GA4のイベントテーブルは入れ子構造を持つ複雑なスキーマなので、この「スキーマを持ち運べる」性質は実務上とても助かります。
退避先のフォルダ構成を先に決めておく
実際に書き出す前に、GCSバケットの中のフォルダ構成を決めておくと、後の運用がぐっと楽になります。おすすめは、年月でフォルダを切る素直な構成です。
たとえば gs://your-bucket/ga4-backup/2024-01/、gs://your-bucket/ga4-backup/2024-02/ のように、月ごとにフォルダを分けておきます。こうしておくと、「2024年1月だけ戻したい」というときに対象が一目でわかりますし、退避と削除の進み具合も、フォルダを眺めるだけで把握できます。
退避の粒度(月単位か日単位か)は、扱うデータ量と「後で戻したくなる単位」で決めます。中小ECの規模なら月単位でまとめても扱いやすいことが多いですが、1日あたりのイベント数が多いサイトでは、日単位に分けておいたほうがファイルが扱いやすくなります。ここは無理に最適化しようとせず、「自分が後で迷わない単位」を優先して構いません。
EXPORT DATA でGCSへ退避する
退避の主役になるのが、SQLだけでファイルを書き出せる EXPORT DATA 文です。クエリの結果を、そのままCloud Storageのバケットへ出力できます。
たとえば、2024年1月分のGA4データをまとめてAvro形式でGCSに書き出すなら、次のようなイメージです。
EXPORT DATA OPTIONS (
uri = 'gs://your-bucket/ga4-backup/2024-01/*.avro',
format = 'AVRO',
overwrite = true
) AS
SELECT *
FROM `your-project.analytics_123456789.events_*`
WHERE _TABLE_SUFFIX BETWEEN '20240101' AND '20240131';
ポイントをいくつか補足します。
uriの末尾にある*(ワイルドカード)が大切です。BigQueryは大きな結果を複数のファイルに分割して書き出すため、出力先には必ずワイルドカードを含めます。これがないとエラーになります。formatにはAVROのほかPARQUET・CSV・JSONなどが指定できます。GA4の入れ子構造をそのまま扱うなら、AvroやParquetが無難です。overwrite = trueは、同じ場所に既にファイルがあったときに上書きする指定です。退避先を月ごとのフォルダで分けておくと、取り違えの事故を防げます。
複数月をまとめてではなく、月ごとや日ごとにファイルを分けて退避しておくと、後で「この月だけ戻したい」というときに扱いやすくなります。退避の単位は、後で戻す単位を想像しながら決めるのがコツです。
なお、EXPORT DATA で走るクエリにも通常どおりスキャン量に応じた課金が発生します。SELECT * で広い期間をスキャンするとそれなりの量になるので、退避作業もコストを意識して、期間を区切って進めるのが安心です。
退避はあわてて一気に進める必要はありません。たとえば「今月は2024年の前半を退避して削除、来月は後半」というように、月をまたいで少しずつ進めても問題ありません。むしろ範囲を区切って進めたほうが、退避後の確認も丁寧にでき、消す前に冷静に判断できます。
EXPORT DATAの基本的な使い方や自動化については、こちらの記事も参考にしてください。
必要なときに再インポートする
GCSへ退避したデータを「もう一度BigQueryで分析したい」となったときは、ファイルをBigQueryのテーブルに読み込み直します。やり方は大きく2つあり、SQLで完結する LOAD DATA 文と、コマンドラインの bq load です。どちらでも同じことができるので、好みや運用に合わせて選んでください。
LOAD DATA 文で戻す
LOAD DATA 文は、GCS上のファイルからテーブルへデータを読み込むSQLです。SQLだけで完結するので、スケジュールクエリなどに組み込みやすいのが利点です。
退避しておいたAvroファイルを、戻し用のテーブルに読み込む例です。
LOAD DATA OVERWRITE `your-project.analytics_restore.events_202401`
FROM FILES (
format = 'AVRO',
uris = ['gs://your-bucket/ga4-backup/2024-01/*.avro']
);
FROM FILES の中では format と uris の指定が必須です。uris は配列で渡すため、角かっこ [ ] で囲みます。複数のパスをまとめて指定することもできます。
先頭の OVERWRITE は、対象テーブルの中身を入れ替える指定です。テーブルがなければ新しく作られ、あれば中身が置き換わります。一方、既存のテーブルにデータを追記したいときは OVERWRITE の代わりに INTO を使います。なお、特定のパーティションだけを差し替えたいときは、LOAD DATA OVERWRITE ... PARTITIONS(...) のように OVERWRITE と PARTITIONS 句を併用するのが基本の形です(INTO は追記)。込み入った戻し方をするときは、構文の細部を最新の公式ドキュメントで確認してください。
bq load コマンドで戻す
コマンドラインでの作業に慣れている場合は、bq load でも同じことができます。シェルスクリプトにまとめておけば、複数の退避ファイルを順番に戻していくような処理も書きやすくなります。
bq load \
--source_format=AVRO \
--replace \
your-project:analytics_restore.events_202401 \
"gs://your-bucket/ga4-backup/2024-01/*.avro"
主なオプションの意味は次のとおりです。
--source_formatは読み込むファイルの形式です。退避時に使った形式(ここではAVRO)に合わせます。--replaceは、対象テーブルの中身を入れ替える指定です。付けなければ既存テーブルへの追記になります。- 最後の2つの引数が「読み込み先テーブル」と「読み込み元のGCSパス」です。テーブルはプロジェクトとデータセットをコロンとドットで区切って指定し、GCSパスにはワイルドカードを使えます。
CSV形式で退避していて、列の型を自動推定させたい場合は --autodetect を付ける、といった選択肢もあります。ただAvroやParquetならファイル自身がスキーマを持っているため、こうした追加指定は基本的に不要です。
LOAD DATA と bq load の使い分け
どちらを使っても結果は同じなので、迷ったら次のような目安で選んでください。
- LOAD DATA 文は、SQLの中で完結します。BigQueryのスケジュールクエリやデータパイプラインの一部として「定期的にファイルを取り込む」ような自動処理に組み込みやすいのが強みです。普段の分析をSQLで進めている人にとっては、文脈を切り替えずに書けるのが気楽です。
- bq load コマンドは、シェルスクリプトやターミナルからの一回限りの作業に向いています。「退避しておいた数か月分を、まとめて順番に戻す」といった処理を
forループで回すような場面では、コマンドのほうが手早く書けます。
どちらか一方を覚えておけば困ることはありません。チームの運用スタイルに合わせて、片方に寄せてしまうのもよい判断です。
スキーマ整合・パーティション復元の注意
再インポートでつまずきやすいのが、スキーマとパーティションまわりです。ここを押さえておくと、戻したデータがそのまま使える状態になります。
まずスキーマ整合についてです。AvroやParquetで退避していれば、列名や型の情報はファイルに含まれているので、読み込み時にそのまま再現されます。これがCSVだと話が変わってきて、CSVはただの文字列の並びなので型情報を持ちません。数値や日付が文字列として読み込まれてしまったり、自動推定が意図と違う型を選んだりすることがあります。GA4のような複雑なスキーマを正確に戻したいなら、退避の時点でAvroかParquetを選んでおくのが結局いちばん安全です。
次にパーティションです。GA4のエクスポートは日付ごとにテーブルが分かれる「シャーディング」方式で、それぞれは普通のテーブルです。これに対して、退避から戻すときに「1つのテーブルを日付で区切る」日付パーティションテーブルとして作り直したい、というケースもあります。その場合は、再インポートのときにパーティションの設定を明示的に指定する必要があります。退避したファイル側にはパーティションの設計情報までは含まれないため、「どの列で区切るか」は戻す側で決めてあげる、と覚えておいてください。
パーティション付きで戻したいときは、bq load なら --time_partitioning_field でパーティションの基準にする列を指定する、LOAD DATA 文なら PARTITION BY 句を添える、といった形になります。また前述のとおり、特定パーティションだけを差し替えたいときは LOAD DATA OVERWRITE ... PARTITIONS(...) のように OVERWRITE と PARTITIONS 句を併用します。込み入ったパーティション運用をするときは、思い込みで書かずに、最新の公式ドキュメントで指定方法と制約をひとつずつ確認しながら進めてください。GA4のシャードをそのまま events_YYYYMMDD という別名のテーブルとして戻すだけなら、パーティションの指定は不要で、シンプルに済みます。
最後に運用上の注意を2点。
- 戻すのは元の場所ではなく、別のデータセットや別名のテーブルにするのが安全です。
analytics_restoreのような復元用データセットを用意しておけば、現役のデータを誤って上書きする事故を防げます。 - 退避が本当に成功しているか、消す前に確認すること。EXPORT DATAでファイルを書き出したら、削除を実行する前に、一度小さく戻してみて行数や中身が想定どおりかを確かめておくと安心です。バックアップは「戻せて初めて完成」です。
まとめ
GA4のBigQueryデータは、古くなったからといって安易に消してしまうと二度と取り戻せません。だからといって全部をBigQueryに抱え込み続けるのもコスト的に重い、というのが多くの中小ECの実情だと思います。
その間を取る現実的な運用が、今回紹介した「GCSへ退避してから消す」という流れです。EXPORT DATA でAvroやParquet形式にしてGCSへ書き出し、必要になったら LOAD DATA や bq load で復元用のデータセットに戻す。退避の形式をスキーマ付きのものにしておくこと、戻し先を別の場所にすること、そして消す前に一度戻せることを確認しておくこと。この3つを押さえておけば、コストと保全性のバランスが取れた運用になります。
改めて全体の流れを並べると、次のようになります。
- 退避先のGCSフォルダ構成を、年月などの分かりやすい単位で決める。
EXPORT DATAで対象期間をAvroまたはParquet形式に書き出す。- 削除する前に、小さく
LOAD DATAまたはbq loadで戻して中身を確認する。 - 問題なければ、BigQuery側の古いテーブルを整理する。
- 後で必要になったら、復元用データセットに戻して分析する。
「古いデータをどう整理するか」で手が止まっているなら、まずは直近で消す予定のない範囲だけでも、一度GCSへ退避する練習をしてみるのがおすすめです。仕組みが一度わかってしまえば、あとは同じことの繰り返しで運用に乗せられます。