BigQuery のテーブルコピーではテーブルプロパティも全てコピーされるのかどうか確認してみた

2020.06.04

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは、みかみです。

前回、BigQuery ではテーブル名やカラム名などの更新はできないことを確認しました。

更新できない項目を変更する場合、既存テーブルのコピーなどで新しいテーブルを作成して差し替える必要がありますが、テーブルをコピーした場合、格納データやテーブルスキーマ以外の description、ラベル情報などのテーブルプロパティも全てコピーされるのかどうか、疑問に思いました。

やりたいこと

  • テーブルコピーでは、テーブルプロパティも全て引き継がれるのか確認したい
  • コピーされないテーブルプロパティがあった場合、プロパティを新テーブルに反映するにはどうすればよいか知りたい

前提

BigQuery Python クライアントライブラリの copy_table でテーブルをコピーした場合の挙動を確認します。

Python クライアントライブラリが実行できる環境は準備済みで、使用するサービスアカウントには copy_table を実行できる権限を付与済みです。

Python クライアントライブラリの Table プロパティ

BigQuery Python クライアントライブラリの Table クラスには、以下のプロパティがあります。

  • project: プロジェクトID
  • dataset_id: データセットID(データセット名)
  • table_id: テーブルID(テーブル名)
  • full_table_id: (プロジェクトID + データセットID + テーブルID )
  • path: テーブル path
  • table_type: テーブル種別( TABLE, VIEW, EXTERNAL のいずれか)
  • location: ロケーション
  • self_link: セルフリンク
  • created: 作成日時
  • modified: 最終更新日時
  • etag: Etag
  • schema: テーブルスキーマ
  • num_bytes: テーブルサイズ
  • num_rows: 行数
  • clustering_fields: クラスタリングフィールド
  • description: 説明
  • friendly_name: 分かりやすい名前
  • expires: 有効期限
  • labels: ラベル
  • partitioning_type: パーティショニング種別
  • time_partitioning: 日時パーティショニング情報
  • range_partitioning: 範囲パーティショニング情報
  • partition_expiration: パーティションの有効期限
  • require_partition_filter: パーティションフィルタ要否
  • external_data_configuration: 外部データソース情報
  • encryption_configuration: カスタム暗号化情報

テーブルコピー時、これらのプロパティも全てコピー先のテーブルに引き継がれるのか、確認してみます。

通常テーブルのプロパティ

まずは通常のテーブルで、コピーによるテーブルプロパティの変更有無を確認してみます。

from google.cloud import bigquery

client = bigquery.Client()

table_id = 'cm-da-mikami-yuki-258308.dataset_1.table_sample'
table = client.get_table(table_id)
table_id_dst = '{}_copy'.format(table_id)
table_dst = bigquery.Table(table_id_dst)

job = client.copy_table(table, table_dst)
print("Starting job {}".format(job.job_id))
job.result()
print("Created table {}".format(table_dst.table_id))

table_dst = client.get_table(table_id_dst)
print('project: {} -> {}'.format(table.project, table_dst.project))
print('dataset_id: {} -> {}'.format(table.dataset_id, table_dst.dataset_id))
print('table_id: {} -> {}'.format(table.table_id, table_dst.table_id))
print('full_table_id: {} -> {}'.format(table.full_table_id, table_dst.full_table_id))
print('path: {} -> {}'.format(table.path, table_dst.path))
print('self_link: {} -> {}'.format(table.self_link, table_dst.self_link))
print('table_type: {} -> {}'.format(table.table_type, table_dst.table_type))
print('location: {} -> {}'.format(table.location, table_dst.location))
print('created: {} -> {}'.format(table.created, table_dst.created))
print('modified: {} -> {}'.format(table.modified, table_dst.modified))
print('etag: {} -> {}'.format(table.etag, table_dst.etag))
print('schema: {} -> {}'.format(table.schema, table_dst.schema))
print('num_bytes: {} -> {}'.format(table.num_bytes, table_dst.num_bytes))
print('num_rows: {} -> {}'.format(table.num_rows, table_dst.num_rows))
print('clustering_fields: {} -> {}'.format(table.clustering_fields, table_dst.clustering_fields))
print('description: {} -> {}'.format(table.description, table_dst.description))
print('friendly_name: {} -> {}'.format(table.friendly_name, table_dst.friendly_name))
print('expires: {} -> {}'.format(table.expires, table_dst.expires))
print('labels: {} -> {}'.format(table.labels, table_dst.labels))
(test_bq) [ec2-user@ip-10-0-43-239 copy_property]$ python copy_table.py
Starting job 67847b80-de91-40f2-b661-e5e7e5ce65b7
Created table table_sample_copy
project: cm-da-mikami-yuki-258308 -> cm-da-mikami-yuki-258308
dataset_id: dataset_1 -> dataset_1
table_id: table_sample -> table_sample_copy
full_table_id: cm-da-mikami-yuki-258308:dataset_1.table_sample -> cm-da-mikami-yuki-258308:dataset_1.table_sample_copy
path: /projects/cm-da-mikami-yuki-258308/datasets/dataset_1/tables/table_sample -> /projects/cm-da-mikami-yuki-258308/datasets/dataset_1/tables/table_sample_copy
self_link: https://bigquery.googleapis.com/bigquery/v2/projects/cm-da-mikami-yuki-258308/datasets/dataset_1/tables/table_sample -> https://bigquery.googleapis.com/bigquery/v2/projects/cm-da-mikami-yuki-258308/datasets/dataset_1/tables/table_sample_copy
table_type: TABLE -> TABLE
location: asia-northeast1 -> asia-northeast1
created: 2020-06-03 10:24:56.788000+00:00 -> 2020-06-03 10:53:00.026000+00:00
modified: 2020-06-03 10:33:36.692000+00:00 -> 2020-06-03 10:53:00.026000+00:00
etag: sS4j+sSrQGI0E6rI3taGVQ== -> xFc1JuaNfeiJnJt9isLDmQ==
schema: [SchemaField('name', 'STRING', 'REQUIRED', '名前', ()), SchemaField('gender', 'STRING', 'NULLABLE', '性別', ()), SchemaField('count', 'INTEGER', 'NULLABLE', '人数', ())] -> [SchemaField('name', 'STRING', 'REQUIRED', '名前', ()), SchemaField('gender', 'STRING', 'NULLABLE', '性別', ()), SchemaField('count', 'INTEGER', 'NULLABLE', '人数', ())]
num_bytes: 654482 -> 654482
num_rows: 34073 -> 34073
clustering_fields: ['gender'] -> ['gender']
description: テーブルコピーのテスト用 -> テーブルコピーのテスト用
friendly_name: どのプロパティがコピーできるか確認 -> どのプロパティがコピーできるか確認
expires: 2020-12-31 19:31:53+00:00 -> None
labels: {'kind': 'test'} -> {'kind': 'test'}

table_idfull_table_idpathself_link は、テーブル名変更に伴い、もちろん元テーブルとは異なります。 また、createdmodifiedetag も、コピー実行に伴い変更されています。

テーブルデータ関連の schemanum_bytesnum_rows はそのままコピーされ、clustering_fieldsdescriptionlabels などのテーブルプロパティも引き継がれています。

ですが、expires はコピーされず、テーブルの有効期限は指定なしに変更されていました。

パーティション関連のプロパティ

パーティショニングテーブルで、パーティション関連情報もコピーできるか確認してみます。

from google.cloud import bigquery

client = bigquery.Client()

table_id = 'cm-da-mikami-yuki-258308.dataset_1.table_sample_partition'
table = client.get_table(table_id)
table_id_dst = '{}_copy'.format(table_id)
table_dst = bigquery.Table(table_id_dst)

job = client.copy_table(table, table_dst)
print("Starting job {}".format(job.job_id))
job.result()
print("Created table {}".format(table_dst.table_id))

table_dst = client.get_table(table_id_dst)
print('partitioning_type: {} -> {}'.format(table.partitioning_type, table_dst.partitioning_type))
print('time_partitioning: {} -> {}'.format(table.time_partitioning, table_dst.time_partitioning))
print('partition_expiration: {} -> {}'.format(table.partition_expiration, table_dst.partition_expiration))
print('require_partition_filter: {} -> {}'.format(table.require_partition_filter, table_dst.require_partition_filter))
(test_bq) [ec2-user@ip-10-0-43-239 copy_property]$ python copy_table_partition.py
Starting job c3628a5b-1541-4eb8-b29b-8b921dda1d97
Created table table_sample_partition_copy
partitioning_type: HOUR -> HOUR
time_partitioning: TimePartitioning(expirationMs=86400000,field=col_3,requirePartitionFilter=True,type=HOUR) -> TimePartitioning(expirationMs=86400000,field=col_3,requirePartitionFilter=True,type=HOUR)
partition_expiration: 86400000 -> 86400000
require_partition_filter: True -> True

テーブルの有効期限である expires はコピーされませんでしたが、パーティションの有効期限 partition_expiration はコピーされました。日付パーティショニングテーブルのパーティション関連プロパティが、コピー先のテーブルにも引き継がれていることが確認できました。

念のため、範囲指定のパーティショニングテーブルで、range_partitioning のコピーも確認してみます。

(test_bq) [ec2-user@ip-10-0-43-239 copy_property]$ python copy_table_partition_2.py
Starting job d39236f5-912b-4035-aded-fb841e602749
Created table pos_partition_val_copy
range_partitioning: RangePartitioning(field='JANCD', range_=PartitionRange(end=5000000000000, interval=100000000, start=4000000000000)) -> RangePartitioning(field='JANCD', range_=PartitionRange(end=5000000000000, interval=100000000, start=4000000000000))

こちらも、コピー元テーブルと同じプロパティが、コピー先テーブルにも引き継がれたことが確認できました。

外部テーブルのプロパティ

データソースに GCS ファイルなどの外部データを指定した外部テーブルの場合でも、テーブルプロパティがコピーされるか確認してみます。

from google.cloud import bigquery

client = bigquery.Client()

table_id = 'cm-da-mikami-yuki-258308.dataset_1.table_external'
table = client.get_table(table_id)
table_id_dst = '{}_copy'.format(table_id)
table_dst = bigquery.Table(table_id_dst)

job = client.copy_table(table, table_dst)
print("Starting job {}".format(job.job_id))
job.result()
print("Created table {}".format(table_dst.table_id))

table_dst = client.get_table(table_id_dst)
print('table_type: {} -> {}'.format(table.table_type, table_dst.table_type))
print('external_data_configuration:')
print('\tsource_format: {} -> {}'.format(table.external_data_configuration.source_format, table_dst.external_data_configuration.source_format))
print('\tsource_uris: {} -> {}'.format(table.external_data_configuration.source_uris, table_dst.external_data_configuration.source_uris))
(test_bq) [ec2-user@ip-10-0-43-239 copy_property]$ python copy_table_external.py
Starting job a5022ade-5fe5-44d4-b6f5-a5010e363529
Traceback (most recent call last):
  File "copy_table_external.py", line 12, in <module>
    job.result()
  File "/home/ec2-user/test_bq/lib/python3.7/site-packages/google/cloud/bigquery/job.py", line 818, in result
    return super(_AsyncJob, self).result(timeout=timeout)
  File "/home/ec2-user/test_bq/lib/python3.7/site-packages/google/api_core/future/polling.py", line 127, in result
    raise self._exception
google.api_core.exceptions.BadRequest: 400 cm-da-mikami-yuki-258308:dataset_1.table_external is not allowed for this operation because it is currently a EXTERNAL.

... is not allowed for this operation because it is currently a EXTERNAL. とのことで、現在のところ、外部テーブルのコピーはサポートされていないそうです。

暗号化キー情報のコピー

BigQuery のテーブルデータはデフォルトでは Google が管理する鍵で暗号化されますが、KMS に登録済みのキーを暗号化キーとして指定することもできます。

KMS の暗号化キーを指定したテーブルの場合、暗号化キー情報もコピーされるのか確認してみます。

from google.cloud import bigquery

client = bigquery.Client()

table_id = 'cm-da-mikami-yuki-258308.dataset_1.table_kms'
table = client.get_table(table_id)
table_id_dst = '{}_copy'.format(table_id)
table_dst = bigquery.Table(table_id_dst)

job = client.copy_table(table, table_dst)
print("Starting job {}".format(job.job_id))
job.result()
print("Created table {}".format(table_dst.table_id))

table_dst = client.get_table(table_id_dst)
print('encryption_configuration:')
print('\tsrc: {}'.format(table.encryption_configuration))
print('\tdst: {}'.format(table_dst.encryption_configuration))
(test_bq) [ec2-user@ip-10-0-43-239 copy_property]$ python copy_table_encrypt.py
Starting job 60337859-8ba6-4ff2-bc8f-109623554422
Created table table_kms_copy
encryption_configuration:
        src: EncryptionConfiguration(projects/cm-da-mikami-yuki-258308/locations/asia-northeast1/keyRings/test-mikami/cryptoKeys/mikami-for-test-update)
        dst: None

データ暗号化キー情報をカスタマイズしている場合、キー情報はコピーされませんでした。

コピーできないテーブルプロパティを新テーブルに引き継ぐには

テーブルコピー時、ほとんどのテーブルプロパティはコピー先テーブルに引き継がれますが、テーブル有効期限( expires )やデータ暗号化のカスタムキー情報( encryption_configuration )は引き継がれませんでした。

では、expiresencryption_configuration も新しいテーブルに引き継ぎたい場合はどうすればよいのか確認してみます。

BigQuery Python クライアントライブラリの copy_table では、job_config パラメータでコピーオプションを指定することができます。

job_config パラメータで指定できる項目を確認したところ、 encryption_configuration はコピーオプションとして指定できそうですが、expires は指定できなさそうです。

encryption_configuration をコピー時に job_config パラメータで指定し、expires は、コピー後に更新してみます。

from google.cloud import bigquery

client = bigquery.Client()

table_id = 'cm-da-mikami-yuki-258308.dataset_1.table_sample_encrypt'
table = client.get_table(table_id)
table_id_dst = '{}_copy'.format(table_id)
table_dst = bigquery.Table(table_id_dst)

copy_config = bigquery.CopyJobConfig()
copy_config.destination_encryption_configuration = table.encryption_configuration
job = client.copy_table(table, table_dst,  job_config=copy_config)
print("Starting job {}".format(job.job_id))
job.result()
print("Created table {}".format(table_dst.table_id))

table_dst = client.get_table(table_id_dst)
table_dst.expires = table.expires
table_dst = client.update_table(table_dst, ['expires'])
print("Updated table: {}".format(table_dst.table_id))

table_dst = client.get_table(table_id_dst)
print('expires: {} -> {}'.format(table.expires, table_dst.expires))
print('encryption_configuration:')
print('\tsrc: {}'.format(table.encryption_configuration))
print('\tdst: {}'.format(table_dst.encryption_configuration))
(test_bq) [ec2-user@ip-10-0-43-239 copy_property]$ python copy_table_encrypt_2.py
Starting job 6bcff046-5745-4d56-a4fb-84ffeb9b7a63
Created table table_sample_encrypt_copy
Updated table: table_sample_encrypt_copy
expires: 2020-12-30 20:53:56+00:00 -> 2020-12-30 20:53:56+00:00
encryption_configuration:
        src: EncryptionConfiguration(projects/cm-da-mikami-yuki-258308/locations/asia-northeast1/keyRings/test-mikami/cryptoKeys/test-cm-mikami)
        dst: EncryptionConfiguration(projects/cm-da-mikami-yuki-258308/locations/asia-northeast1/keyRings/test-mikami/cryptoKeys/test-cm-mikami)

新テーブルに、元テーブルと同じプロパティが付与できました。

また、外部テーブルの場合、コピー処理自体実行できませんでした。 同じテーブルプロパティを持ち、同じ外部データソースを参照するテーブルが欲しい場合、テーブルを新規作成する必要があります。

from google.cloud import bigquery

client = bigquery.Client()

table_id = 'cm-da-mikami-yuki-258308.dataset_1.table_external'
table = client.get_table(table_id)

table_id_dst = '{}_copy'.format(table_id)
table_dst = bigquery.Table(table_id_dst, table.schema)
table_dst.description = table.description
table_dst.expires = table.expires
table_dst.labels = table.labels
table_dst.external_data_configuration = table.external_data_configuration
table_dst = client.create_table(table_dst)
print("Created table {}".format(table_dst.table_id))

table_dst = client.get_table(table_id_dst)
print('table_type: {} -> {}'.format(table.table_type, table_dst.table_type))
print('schema: {} -> {}'.format(table.schema, table_dst.schema))
print('description: {} -> {}'.format(table.description, table_dst.description))
print('expires: {} -> {}'.format(table.expires, table_dst.expires))
print('labels: {} -> {}'.format(table.labels, table_dst.labels))
print('external_data_configuration:')
print('\source_format: {} -> {}'.format(table.external_data_configuration.source_format, table_dst.external_data_configuration.source_format))
print('\source_uris: {} -> {}'.format(table.external_data_configuration.source_uris, table_dst.external_data_configuration.source_uris))
(test_bq) [ec2-user@ip-10-0-43-239 copy_property]$ python copy_table_external_2.py
Created table table_external_copy
table_type: EXTERNAL -> EXTERNAL
schema: [SchemaField('name', 'STRING', 'NULLABLE', None, ()), SchemaField('gender', 'STRING', 'NULLABLE', None, ()), SchemaField('count', 'INTEGER', 'NULLABLE', None, ())] -> [SchemaField('name', 'STRING', 'NULLABLE', None, ()), SchemaField('gender', 'STRING', 'NULLABLE', None, ()), SchemaField('count', 'INTEGER', 'NULLABLE', None, ())]
description: 外部テーブル -> 外部テーブル
expires: 2020-12-30 19:34:12+00:00 -> 2020-12-30 19:34:12+00:00
labels: {'kind': 'test'} -> {'kind': 'test'}
external_data_configuration:
\source_format: CSV -> CSV
\source_uris: ['gs://test-mikami/data_test/yob2010.txt'] -> ['gs://test-mikami/data_test/yob2010.txt']

元テーブルのプロパティを参照して、同じプロパティを持つ外部テーブルを作成することができました。

まとめ(所感)

以下のプロパティは、デフォルトではコピー先テーブルには引き継がれないため、別途オプション指定やコピー後に update が必要でした。

  • expires
  • encryption_configuration

また、テーブル種別が EXTERNAL のテーブルの場合、コピーはサポートされていないため、テーブルの新規作成が必要でした。

テーブル名などの更新用途でテーブルコピー処理を検討する場合、テーブル種別や一部のプロパティには別途考慮する必要がありそうです。

参考