BigQuery のテーブルコピーではテーブルプロパティも全てコピーされるのかどうか確認してみた
こんにちは、みかみです。
前回、BigQuery ではテーブル名やカラム名などの更新はできないことを確認しました。
更新できない項目を変更する場合、既存テーブルのコピーなどで新しいテーブルを作成して差し替える必要がありますが、テーブルをコピーした場合、格納データやテーブルスキーマ以外の description、ラベル情報などのテーブルプロパティも全てコピーされるのかどうか、疑問に思いました。
やりたいこと
- テーブルコピーでは、テーブルプロパティも全て引き継がれるのか確認したい
- コピーされないテーブルプロパティがあった場合、プロパティを新テーブルに反映するにはどうすればよいか知りたい
前提
BigQuery Python クライアントライブラリの copy_table
でテーブルをコピーした場合の挙動を確認します。
Python クライアントライブラリが実行できる環境は準備済みで、使用するサービスアカウントには copy_table
を実行できる権限を付与済みです。
- copy_table | Python Client for Google BigQuery
- クイックスタート: クライアント ライブラリの使用 | BigQuery ドキュメント
- 必要な権限 | BigQuery ドキュメント
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_id
、full_table_id
、path
、self_link
は、テーブル名変更に伴い、もちろん元テーブルとは異なります。
また、created
、modified
、etag
も、コピー実行に伴い変更されています。
テーブルデータ関連の schema
や num_bytes
、 num_rows
はそのままコピーされ、clustering_fields
、description
、labels
などのテーブルプロパティも引き継がれています。
ですが、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
)は引き継がれませんでした。
では、expires
や encryption_configuration
も新しいテーブルに引き継ぎたい場合はどうすればよいのか確認してみます。
BigQuery Python クライアントライブラリの copy_table
では、job_config
パラメータでコピーオプションを指定することができます。
- copy_table | Python Client for Google BigQuery
- google.cloud.bigquery.job.CopyJobConfig | Python Client for Google BigQuery
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
のテーブルの場合、コピーはサポートされていないため、テーブルの新規作成が必要でした。
テーブル名などの更新用途でテーブルコピー処理を検討する場合、テーブル種別や一部のプロパティには別途考慮する必要がありそうです。