将数据导出到 Bigtable(反向 ETL)

本文档介绍如何设置从 BigQuery 到 Bigtable 的反向 ETL (RETL)。为此,您可以使用 EXPORT DATA 语句将数据从 BigQuery 表导出到 Bigtable 表。

您可以使用到 Bigtable 的 RETL 工作流,将 BigQuery 的分析功能与 Bigtable 的低延迟和高吞吐量相结合。该工作流可让您向应用用户提供数据,而不会耗尽 BigQuery 的配额和限制。

Bigtable 表的特性

Bigtable 表与 BigQuery 表的不同之处体现在以下几个方面:

  • Bigtable 表和 BigQuery 表均由行组成,但 Bigtable 行是由行关键字和列族组成的,这些列族具有属于同一列族的任意数量的列。
  • 给定表的列族在创建表时创建,但也可以稍后添加或移除。创建列族时,无需指定属于它的列。
  • Bigtable 列无需提前定义,并可用于以其名称(也称为限定符)存储在表中的数据大小限制内的数据。
  • Bigtable 列可以具有在表中的数据大小限制内的任何二进制值。
  • Bigtable 列始终具有时间维度(也称为版本)。只要时间戳不相同,可以将任意数量的行存储在同一列的任何行中。
  • Bigtable 时间戳以微秒为单位,从 Unix 纪元时间开始计算 - 例如,0 表示 1970-01-01T00:00:00 UTC。时间戳必须是毫秒粒度的非负微秒数(只接受 1000μs 的倍数)。默认 Bigtable 时间戳为 0。
  • 在 Bigtable 中,数据通过行键、多个行键、行键范围或使用过滤条件进行读取。除全表扫描外,所有类型的读取请求至少需要一个行键或行键范围。

如需了解如何准备 BigQuery 结果以导出到 Bigtable,请参阅准备查询结果以进行导出

准备工作

您必须创建 Bigtable 实例Bigtable 表来接收导出的数据。

授予为用户提供执行本文档中的每个任务所需权限的 Identity and Access Management (IAM) 角色

所需的角色

如需获得将 BigQuery 数据导出到 Bigtable 所需的权限,请让管理员向您授予项目的以下 IAM 角色:

如需详细了解如何授予角色,请参阅管理对项目、文件夹和组织的访问权限

您也可以通过自定义角色或其他预定义角色来获取所需的权限。

限制

位置注意事项

  • 如果 BigQuery 数据集位于多区域,则 Bigtable 应用配置文件必须配置为将数据路由到该多区域内的 Bigtable 集群。例如,如果 BigQuery 数据集位于 US 多区域,则 Bigtable 集群可以位于美国境内的 us-west1(俄勒冈)区域。
  • 如果 BigQuery 数据集位于单个区域,则 Bigtable 应用配置文件必须配置为将数据路由到同一区域中的 Bigtable 集群。例如,如果 BigQuery 数据集位于 asia-northeast1(东京)区域,则 Bigtable 集群也必须位于 asia-northeast1(东京)区域。

如需了解详情,请参阅 Bigtable 位置

受支持的 BigQuery 类型

以下类型的数据写入 Bigtable 时受支持:

BigQuery 类型 写入的 Bigtable 值
BYTES 按原样导出。
STRING 转换为 BYTES
INTEGER 如果 bigtable_options.column_families.encoding 设置为 BINARY,则该值采用 8 字节的大端字节序格式(最高有效字节优先)写入。如果 bigtable_options.column_families.encoding 设置为 TEXT,则该值会写为表示数字的人类可读字符串。
FLOAT 以 IEEE 754 8 字节输出格式写入值。
BOOLEAN 如果 bigtable_options.column_families.encoding 设置为 BINARY,则该值将写入为 1 字节值(false = 0x00 或 true = 0x01)。如果 bigtable_options.column_families.encoding 设置为 TEXT,则该值将写入为文本("true""false")。
JSON
导出的 JSON 类型的列将被解释为属于特定 Bigtable 列族的一组列。JSON 对象的成员被解释为列,其值将写入 Bigtable。您可以使用 bigtable_options 配置调整要写入的列的名称。

例如:
    JSON '{"FIELD1": "VALUE1", "FIELD2": "VALUE2"}' as MY_COLUMN_FAMILY
    
其中,VALUE1VALUE2 值作为 FIELD1FIELD2 列写入 Bigtable 列族 MY_COLUMN_FAMILY
STRUCT
导出的 STRUCT 类型的列将被解释为属于特定 Bigtable 列族的一组列。结构体的成员会被解释为列,其值将写入 Bigtable。您可以使用 bigtable_options 配置调整要写入的列的名称。

例如:
    STRUCT<FIELD1  STRING, FIELD2 INTEGER> as MY_COLUMN_FAMILY
    
其中,FIELD1FIELD2 值作为 FIELD1FIELD2 列写入 Bigtable 列族 MY_COLUMN_FAMILY

这些支持的数据类型类似于从 BigQuery 的外部 Bigtable 表中读取数据。

Bigtable 中的 NULL

Bigtable 中的 NULL 值具有以下限制:

  • Bigtable 没有与 NULL 值对应的模拟。如果在 Bigtable 中导出给定列族和列的 NULL 值,则会从 Bigtable 行中删除当前值。

  • 如果导出之前不存在具有给定行键、列族、列限定符和时间戳的 Bigtable 值,则导出的 NULL 值对 Bigtable 行没有影响。

  • 导出 STRUCTJSON 类型的 NULL 值时,属于受影响行的所有列族的所有列值都会被删除。您应将 NULL 值转换为 STRUCTJSON 类型,以便 SQL 引擎为其附加正确的类型。以下查询会删除具有一组指定行键的列族 column_family1 中的所有数据:

    EXPORT DATA OPTIONS (...) AS
    SELECT
      rowkey,
    CAST(NULL as STRUCT<INT64>) AS column_family1 FROM T
  • 在导出期间,系统会跳过具有 NULL 行键的行。在导出统计信息中,跳过的行数会返回给调用方。

使用 bigtable_options 配置导出

您可以在导出期间使用 bigtable_options 配置来弥合 BigQuery 和 Bigtable 存储模型之间的差异。配置以 JSON 字符串的形式表示,如以下示例所示:

EXPORT DATA OPTIONS(
   uri="https://bigtable.googleapis.com/projects/PROJECT_ID/instances/INSTANCE_ID/appProfiles/APP_PROFILE_ID/tables/TABLE",
   bigtable_options = """{
     "columnFamilies": [{
       "familyId": "COLUMN_FAMILY_NAME",
       "encoding": "ENCODING_VALUE",
       "columns": [
         {
           "qualifierString": "BIGTABLE_COLUMN_QUALIFIER",
           ["qualifierEncoded": "BASE_64_ENCODED_VALUE",]
           "fieldName": "BIGQUERY_RESULT_FIELD_NAME"
         }
       ]
    }]
   }"""
)

下表介绍了 bigtable_options 配置中使用的可能字段:

字段名称 说明
columnFamilies 列族描述符的数组。
columnFamilies.familyId Bigtable 列族的标识符。
columnFamilies.encoding 值可以设置为 BINARYTEXT。如需了解如何对类型进行编码,请参阅受支持的 BigQuery 类型
columnFamilies.columns Bigtable 列映射的数组。
columnFamilies.columns.qualifierString 可选:Bigtable 列限定符。如果列限定符没有非 UTF-8 代码,请指定此值。qualifierStringqualifierEncoding 字段是互斥的。如果既未指定 qualifierString,也未指定 qualifierEncoded,则使用 fieldName 作为列限定符。
columnFamilies.columns.qualifierEncoded 可选:Base64 编码的列限定符。与 qualifierString 类似,前提是列限定符必须具有非 UTF-8 代码。
columnFamilies.columns.fieldName 必需:BigQuery 结果集字段名称。在某些情况下,可为空字符串。如需查看如何将空 fieldName 值与简单类型字段结合使用的示例,请参阅准备查询结果以进行导出

准备查询结果以进行导出

如需将查询结果导出到 Bigtable,结果必须满足以下要求:

  • 结果集必须包含类型为 STRINGBYTES 的列 rowkey
  • 行键、列限定符、值和时间戳不得超过 Bigtable 表中的数据大小限制
  • 结果集中除了 rowkey 之外必须至少还有一列。
  • 每个结果集列都必须是受支持的 BigQuery 类型之一。必须先将任何不受支持的列类型转换为某种受支持的类型,然后再导出到 Bigtable。

Bigtable 不要求列限定符是有效的 BigQuery 列名称,并且 Bigtable 支持使用任何字节。如需了解如何替换导出的目标列限定符,请参阅使用 bigtable_options 配置导出

如果您将导出的值与 Bigtable API(例如 ReadModifyWriteRow)搭配使用,则任何数值都必须使用正确的二进制编码

默认情况下,STRUCTJSON 以外类型的独立结果列会被解释为目标列族的值等于结果列名称,列限定符等于空字符串。

为了演示如何写入这些数据类型,请考虑以下 SQL 示例,其中 columncolumn2 是独立的结果列:

SELECT
  x as column1, y as column2
FROM table

在此示例查询中,在处理除 JSONSTRUCT 之外的类型时,SELECT x as column1 将数据写入 Bigtable 中的 column1 列族和 ''(空字符串)列限定符下。

您可以使用 bigtable_options 配置来更改在导出中写入这些类型的方式,如以下示例所示:

EXPORT DATA OPTIONS (
  
  bigtable_options="""{
   "columnFamilies" : [
      {
        "familyId": "ordered_at",
        "columns": [
           {"qualifierString": "order_time", "fieldName": ""}
        ]
      }
   ]
}"""
) AS
SELECT
  order_id as rowkey,
  STRUCT(product, amount) AS sales_info,
  EXTRACT (MILLISECOND FROM order_timestamp AT TIME ZONE "UTC") AS ordered_at
FROM T

在此示例中,BigQuery 表 T 包含以下行:

order_id order_timestamp product amount
101 2023-03-28T10:40:54Z 操纵杆 2

如果将上述 bigtable_options 配置与表 T 搭配使用,系统会将以下数据写入 Bigtable:

rowkey sales_info(列族) ordered_at(列族)
101 产品 金额 order_time
1970-01-01T00:00:00Z 操纵杆 1970-01-01T00:00:00Z 2 1680000054000

1680000054000 表示 2023-03-28T10:40:54Z,以毫秒为单位,从世界协调时间 (UTC) 时区从 Unix 纪元时间算起。

使用 _CHANGE_TIMESTAMP 为行中的所有单元格设置时间戳

您可以将 TIMESTAMP 类型的 _CHANGE_TIMESTAMP 列添加到要导出的结果中。写入 Bigtable 的每个单元格都会使用导出结果行的 _CHANGE_TIMESTAMP 中的时间戳值。

Bigtable 不支持 Unix 纪元 (1970-01-01T00:00:00Z) 之前的时间戳。如果 _CHANGE_TIMESTAMP 值为 NULL,则将 Unix 纪元时间 0 用作默认时间戳值。

以下查询为表 Torder_timestamp 列中指定的时间戳写入 productamount 列的单元。

EXPORT DATA OPTIONS (...) AS
SELECT
  rowkey,
  STRUCT(product, amount) AS sales_info,
  order_timestamp as _CHANGE_TIMESTAMP
FROM T

持续导出

如果您想持续处理导出查询,可以将其配置为持续查询

导出具有相同 rowkey 值的多个结果

当您导出包含具有相同 rowkey 值的多个行的结果时,写入 Bigtable 的值最终将位于同一个 Bigtable 行中。

您可以使用此方法在同一行中生成多个列值版本。在此示例中,BigQuery 中的 orders 表包含以下数据:

id customer order_timestamp amount_spent
100 小博 2023-01-01T10:10:54Z 10.99
101 Alice 2023-01-02T12:10:50Z 102.7
102 小博 2023-01-04T15:17:01Z 11.1

然后,用户执行以下 EXPORT DATA 语句:

EXPORT DATA OPTIONS (
uri="https://bigtable.googleapis.com/projects/PROJECT-ID/instances/INSTANCE-ID/appProfiles/APP_PROFILE_ID/tables/TABLE",
format="CLOUD_BIGTABLE"
) AS
SELECT customer as rowkey, STRUCT(amount_spent) as orders_column_family, order_timestamp as _CHANGE_TIMESTAMP
FROM orders

将此语句与 BigQuery orders 表搭配使用会将以下数据写入 Bigtable:

orders_column_family
行键 amount_spent
Alice 2023-01-02T12:10:50Z 102.7
小博 2023-01-01T10:10:54Z 10.99
2023-01-04T15:17:01Z 11.1

导出到 Bigtable 会将新值合并到表中,而不是替换整行。如果 Bigtable 中已存在行键的值,则新值可以部分或完全替换前面的值,具体取决于写入的单元格的列族、列名称和时间戳。

将多个列导出为协议缓冲区 (Protobuf) 值

协议缓冲区为序列化结构化数据提供了一种灵活而高效的机制。考虑到 BigQuery 和 Bigtable 之间处理不同类型的方式不同,导出为 Protobuf 会很有帮助。您可以使用 BigQuery 用户定义的函数 (UDF) 将数据作为 Protobuf 二进制值导出到 Bigtable。如需了解详情,请参阅将数据导出为 Protobuf 列

导出优化

您可以通过修改 Bigtable 目标集群中的节点数来更改将记录从 BigQuery 导出到 Bigtable 的吞吐量。吞吐量(每秒写入的行数)会随着目标集群中的节点数线性扩缩。例如,如果您将目标集群中的节点数增加一倍,则导出吞吐量大致会增加一倍。

价格

当您从标准查询中导出数据时,您需要按照数据提取价格付费。当您从持续查询中导出数据时,您需要按照 BigQuery 容量计算价格付费。如需运行持续查询,您必须拥有使用企业版或企业 Plus 版预留以及使用 CONTINUOUS 作业类型的预留分配

导出数据后,如果您将数据存储在 Bigtable 中,则需要为此付费。如需了解详情,请参阅 Bigtable 价格