BigQuery のテーブルをコピーすると有効期限が引き継がれる場合があるから気をつけろ! - エムスリーテックブログ

エムスリーテックブログ

エムスリー(m3)のエンジニア・開発メンバーによる技術ブログです

BigQuery のテーブルをコピーすると有効期限が引き継がれる場合があるから気をつけろ!

【デジカルチーム ブログリレー 2 日目】

クラウド型電子カルテデジカルチームの井上 (@wtr_in) です。

言いたいことはタイトルでほぼ言い切ってしまいましたが、先日 BigQuery 上のデータをうっかり消してしまいそうになる事件がありました。原因はテーブルコピーに関する細かい仕様の勘違いだったのですが、もしかすると今後同じようにハマる人もいるかもしれないので、せっかくなので書き残しておきます。

BigQuery に関する前提知識

本題に入る前に、BigQuery についての前提知識をおさらいします。BigQuery に詳しい方は 本題 まで飛ばしていただいて問題ありません。

1. BigQuery のテーブル有効期限機能

BigQuery にはテーブル単位で有効期限を設定する機能があります。有効期限を設定した場合、期限を過ぎるとテーブルが自動削除されます。

docs.cloud.google.com

例えば、日次でテーブルを作っていて、1 ヶ月前までのテーブルは取っておきたいが、それ以前のものは不要なので削除したい、というケースを考えます。この場合、テーブル作成時に有効期限を 1 ヶ月後に設定してあげれば、わざわざ利用者側で定期削除などを行わなくて良いので便利です。

2. 今や bq cp で異なるリージョンにもテーブルコピーできる

BigQuery では長年、リージョン(ロケーション)が異なる複数のデータセットを扱う場合に様々な制約がありましたが、近年のアップデートによって、その制約が解消されつつあります。

代表的な例として、リージョンが異なるデータセットにまたがって直接 JOIN などのクエリを実行できない、というのが長年の BigQuery の常識だったのですが、今年プレビュー提供が開始されたグローバルクエリ機能を使うと、直接クロスリージョンでクエリが実行できるようになりました*1

またもう一つの例として、リージョンの異なるデータセット間でテーブルをコピーするのも、かつては GCS を経由したり、Data Transfer Service を使ったりとひと手間かかっていました。しかしこちらも、2023 年に bq cp コマンドでのリージョン間テーブルコピー 機能がプレビュー提供開始されたことで、割と気軽にコピーができるようになりました*2

本題: テーブルコピーでの有効期限の引き継ぎに気をつけろ!

さて、前提がおさらいできたところで本題です。

通常、テーブル単位での有効期限が設定されたテーブルを bq cp や各言語の SDK でコピーした場合、有効期限は引き継がれません*3。試してみるなら以下のような感じになります。

PROJECT_ID=<YOUR_PROJECT_ID>
bq mk --dataset --location=US ${PROJECT_ID}:test_dataset_us

# 1 時間後期限のテーブルを作成
bq mk --table --expiration 3600 ${PROJECT_ID}:test_dataset_us.source_table name:STRING,value:INTEGER
bq show --format=prettyjson ${PROJECT_ID}:test_dataset_us.source_table | grep -A1 expirationTime
=> # 期限が設定されている
  "expirationTime": "1781233620000",
  "id": "${PROJECT_ID}:test_dataset_us.source_table",

# テーブルをコピー
bq cp ${PROJECT_ID}:test_dataset_us.source_table ${PROJECT_ID}:test_dataset_us.dest_table
bq show --format=prettyjson ${PROJECT_ID}:test_dataset_us.dest_table | grep -A1 expirationTime
=> # 期限は引き継がれていないので何も出ない

ところが、これを リージョンをまたいで bq cp した場合には、通常のリージョン内でのコピーと異なり コピー元テーブルで設定されていたテーブル有効期限が引き継がれる という挙動になります!*4

PROJECT_ID=<YOUR_PROJECT_ID>
# test_dataset_us は最初の例で作成済みの前提(未作成の場合は最初の例を参照)
bq mk --dataset --location=asia-northeast1 ${PROJECT_ID}:test_dataset_jp

# 1 時間後期限のテーブルを作成
bq mk --table --expiration 3600 ${PROJECT_ID}:test_dataset_jp.source_table name:STRING,value:INTEGER
bq show --format=prettyjson ${PROJECT_ID}:test_dataset_jp.source_table | grep -A1 expirationTime
=> # 期限が設定されている
  "expirationTime": "1781233624000",
  "id": "${PROJECT_ID}:test_dataset_jp.source_table",

# テーブルをコピー(クロスリージョンコピーの警告が出る)
bq cp ${PROJECT_ID}:test_dataset_jp.source_table ${PROJECT_ID}:test_dataset_us.dest_table_from_jp
**** NOTE! ****
Warning: This operation is a cross-region copy operation. This may incur additional charges and take a long time to complete.
This command is running in sync mode. It is recommended to use async mode (-sync=false) for cross-region copy operation.
cp: Proceed with cross-region copy of ${PROJECT_ID}:test_dataset_jp.source_table? [y/N]: y
Waiting on bqjob_r405771497b263f8f_0000019eb9956f0e_1 ... (9s) Current status: DONE
Table '${PROJECT_ID}:test_dataset_jp.source_table' successfully copied to '${PROJECT_ID}:test_dataset_us.dest_table_from_jp'

bq show --format=prettyjson ${PROJECT_ID}:test_dataset_us.dest_table_from_jp | grep -A1 expirationTime
=> # source_table と同じ期限が設定されている!
  "expirationTime": "1781233624000",
  "id": "${PROJECT_ID}:test_dataset_us.dest_table_from_jp",

通常のリージョン内でのテーブルコピーとはかなり異なる仕組みでコピーされていることが推測されるので、それに起因する仕様の違いだとは思うのですが、そこそこ BigQuery を触っていて、有効期限は引き継がれないと思い込んでいると逆にハマってしまう罠です。

なお、この有効期限の引き継ぎについて公式ドキュメントのテーブルコピーの制限事項も確認してみましたが、明確な記載は見当たりませんでした(2026 年 6 月時点)。ドキュメント化されていない挙動のようなので、将来変わる可能性がある点にもご注意ください。

もしリージョン内コピーの挙動に合わせてコピー先のテーブルで期限を削除したい場合は、コピー後に明示的に期限を削除すれば OK です。

bq update --expiration 0 ${PROJECT_ID}:test_dataset_us.dest_table_from_jp
bq show --format=prettyjson ${PROJECT_ID}:test_dataset_us.dest_table_from_jp | grep -A1 expirationTime
=> # 期限は消えたので何も出ない

有効期限以外の違いはあるか?

おまけですが、テーブルの有効期限以外で挙動の違いはあるのかも気になったので確認してみました。

結論としては、リージョン内コピーとクロスリージョンコピーで挙動が変わるのは、テーブル有効期限だけでした。

設定項目 リージョン内コピー クロスリージョンコピー
テーブル有効期限 ✕ 引き継がれない 〇 引き継がれる
パーティション有効期限 〇 引き継がれる 〇 引き継がれる
パーティション設定 〇 引き継がれる 〇 引き継がれる
クラスタリング 〇 引き継がれる 〇 引き継がれる
テーブル / カラムの description 〇 引き継がれる 〇 引き継がれる
ラベル 〇 引き継がれる 〇 引き継がれる

こう見ると、リージョン内でテーブルの有効期限が引き継がれないのがどちらかといえば例外的な挙動ではあるようです。

まとめ

  • クロスリージョンでの bq cp では、リージョン内コピーと異なり、コピー元テーブルの有効期限がそのまま引き継がれる
  • 引き継がれた有効期限が不要な場合は、コピー後に bq update --expiration 0 で削除すれば OK
  • 有効期限以外の主要な設定は、リージョン内・クロスリージョンのどちらのコピーでも同様に引き継がれる

bq cp でのクロスリージョンコピーは便利な機能ですが、テーブル有効期限を使っている場合は気をつけましょう。

We are Hiring!

エムスリーでは、クラウド型電子カルテを開発したい方はもちろん、BigQuery などを使ったデータエンジニアリングが好きな方も絶賛募集中です!

興味を持っていただけた方、カジュアル面談や採用へのご応募をお待ちしています。

jobs.m3.com

*1:追加料金がかかるなど、注意事項はあるので、利用の際は公式ドキュメントを参照ください。

*2:こちらも追加料金がかかるので、利用の際はドキュメントを確認ください。

*3:なお、コピー先データセットにデフォルトのテーブル有効期限が設定されている場合は、コピー元の有効期限の有無に関わらず、そのデフォルト有効期限がコピー先テーブルに適用されます。

*4:こちらはコピー先データセットにデフォルトのテーブル有効期限が設定されていても、それは無視されてコピー元の有効期限がそのまま引き継がれます。逆にコピー元に有効期限がない場合は、コピー先データセットのデフォルト有効期限も適用されず、有効期限なしのテーブルが作成されます。