查询 Cloud Bigtable 数据

本页介绍如何使用 BigQuery 查询 Cloud Bigtable 中存储的数据。

Cloud Bigtable 是 Google 推出的一种稀疏填充的 NoSQL 数据库,可以扩容到数十亿行和数千列,支持存储 PB 级的数据。Cloud Bigtable 具有与 Apache HBase 类似的数据模型,提供与 HBase 兼容的客户端库。如需了解 Cloud Bigtable 数据模型,请参阅存储模型

支持的区域和地区

目前只有以下区域和地区支持查询 Cloud Bigtable 中的数据:
区域 地区
us-central1

us-central1-a

us-central1-b

us-central1-c

us-central1-f

europe-west1

europe-west1-b

europe-west1-c

europe-west1-d

检索 Cloud Bigtable URI

如需为 Cloud Bigtable 数据源创建外部表,您必须提供 Cloud Bigtable URI。检索 Cloud Bigtable URI 的方法如下:

  1. 打开 Cloud Bigtable 控制台。

    打开 Cloud Bigtable 控制台

  2. 检索有关 Cloud Bigtable 数据源的如下详细信息:

    • 您的项目 ID
    • 您的 Cloud Bigtable 实例 ID
    • 您的 Cloud Bigtable 表的名称
  3. 使用以下格式撰写 Cloud Bigtable URI,各变量含义如下:

    • project_id 是包含您的 Cloud Bigtable 实例的项目
    • instance_id 是 Cloud Bigtable 实例 ID
    • table_name 是您要查询的表的名称

    https://googleapis.com/bigtable/projects/project_id/instances/instance_id/tables/table_name

访问控制和范围

永久外部表的访问权限控制

您可以共享对链接到 Cloud Bigtable 数据源的永久外部表的访问权限,但不能共享对临时外部表的访问权限。

您可以与用户(包括服务帐号)或组共享对永久外部表的访问权限。如需查询外部表,(至少)需要为用户或组授予以下角色:

  • 数据集级别或更高级别的 bigquery.dataViewer 角色,可访问包含外部表的数据集
  • 项目级别或更高级别的 bigquery.user 角色,可运行查询作业
  • Cloud Bigtable 中的 bigtable.reader 角色,可提供对元数据和表的只读权限

Compute Engine 实例的范围

创建 Compute Engine 实例时,您可以为该实例指定一个范围列表。这些范围用于控制实例对 Google Cloud 产品(包括 Cloud Bigtable)的访问权限。在虚拟机上运行的应用使用服务帐号来调用 Google Cloud API。

如果您将某个 Compute Engine 实例设置为以服务帐号身份运行,并且该服务帐号访问一个链接到 Cloud Bigtable 数据源的外部表,则您必须为该实例添加 Cloud Bigtable 只读数据访问权限范围 (https://www.googleapis.com/auth/bigtable.data.readonly)。如需了解详情,请参阅为 Cloud Bigtable 创建 Compute Engine 实例

如需了解如何将范围应用于 Compute Engine 实例,请参阅更改实例的服务帐号和访问权限范围。如需详细了解 Compute Engine 服务帐号,请参阅服务帐号

永久外部表与临时外部表

您可以使用永久表或临时表在 BigQuery 中查询外部数据源。永久表是在数据集中创建的表,该表链接到外部数据源。由于该表是永久性的,因此您可以使用访问权限控制与其他同样有权访问底层外部数据源的人员共享该表,还可以随时查询该表。

使用临时表查询外部数据源时,您需要提交一个命令,该命令必须包含查询并创建一个链接到外部数据源的非永久表。使用临时表时,不会在任何 BigQuery 数据集内创建表。由于该表并非永久存储在数据集内,因此无法与他人共享。如果要对外部数据进行一次性临时查询或执行提取、转换和加载 (ETL) 过程,则使用临时表查询外部数据源非常有用。

使用永久外部表查询 Cloud Bigtable 数据

如需使用永久表查询 Cloud Bigtable 数据源,您需要在 BigQuery 数据集中创建一个链接到 Cloud Bigtable 数据源的表。数据不会存储在 BigQuery 表中。由于该表是永久表,因此您可以使用数据集级层的访问权限控制与其他同样有权访问底层 Cloud Bigtable 数据源的人员共享该表。

在 BigQuery 中创建链接到 Cloud Bigtable 数据源的永久外部表时,您可以通过两种方法指定表架构:

  • 如果您使用的是 API 或 bq 命令行工具,则需要创建表定义文件,以定义外部数据源的架构和元数据。
  • 如果您使用的是 Cloud Console,请手动输入 Cloud Bigtable 列族和限定符。

如需使用永久外部表查询 Cloud Bigtable 数据,请执行以下操作:

  • 创建表定义文件(适用于 API 或 bq 命令行工具)
  • 在 BigQuery 中创建一个链接到外部数据源的表
  • 使用永久表查询数据

创建和查询永久外部表

如需创建和查询永久表,请按如下所述操作:

控制台

目前,Cloud Console 不支持从 Cloud Bigtable 查询数据。

bq

您可以在 bq 命令行工具中使用 bq mk 命令创建表。使用 bq 命令行工具创建链接到外部数据源的表时,您可以使用表定义文件来标识表的架构。

  1. 使用 bq mk 命令创建永久表。

    bq mk \
    --external_table_definition=definition_file \
    dataset.table
    

    其中:

    • definition_file 是本地机器上表定义文件的路径。
    • dataset 是包含该表的数据集的名称。
    • table 是您要创建的表的名称。

    接着,您可以对该表运行查询,就像对原生 BigQuery 表运行查询一样,但需遵守外部数据源的限制

API

  • 对于表资源中的 sourceUris 属性,您只能指定一个 Cloud Bigtable URI,并且此 URI 必须是完整、有效的 HTTPS 网址。

  • 通过设置 sourceFormat 属性来指定数据格式属性。对于 Cloud Bigtable,请指定 "BIGTABLE"

Java

试用此示例之前,请按照《BigQuery 快速入门:使用客户端库》中的 Java 设置说明进行操作。 如需了解详情,请参阅 BigQuery Java API 参考文档

import com.google.cloud.bigquery.BigQuery;
import com.google.cloud.bigquery.BigQueryException;
import com.google.cloud.bigquery.BigQueryOptions;
import com.google.cloud.bigquery.BigtableColumn;
import com.google.cloud.bigquery.BigtableColumnFamily;
import com.google.cloud.bigquery.BigtableOptions;
import com.google.cloud.bigquery.ExternalTableDefinition;
import com.google.cloud.bigquery.QueryJobConfiguration;
import com.google.cloud.bigquery.TableId;
import com.google.cloud.bigquery.TableInfo;
import com.google.cloud.bigquery.TableResult;
import com.google.common.collect.ImmutableList;
import org.apache.commons.codec.binary.Base64;

// Sample to queries an external bigtable data source using a permanent table
public class QueryExternalBigtablePerm {

  public static void main(String[] args) {
    // TODO(developer): Replace these variables before running the sample.
    String projectId = "MY_PROJECT_ID";
    String bigtableInstanceId = "MY_INSTANCE_ID";
    String bigtableTableName = "MY_BIGTABLE_NAME";
    String bigqueryDatasetName = "MY_DATASET_NAME";
    String bigqueryTableName = "MY_TABLE_NAME";
    String sourceUri =
        String.format(
            "https://googleapis.com/bigtable/projects/%s/instances/%s/tables/%s",
            projectId, bigtableInstanceId, bigtableTableName);
    String query = String.format("SELECT * FROM %s ", bigqueryTableName);
    queryExternalBigtablePerm(bigqueryDatasetName, bigqueryTableName, sourceUri, query);
  }

  public static void queryExternalBigtablePerm(
      String datasetName, String tableName, String sourceUri, String query) {
    try {
      // Initialize client that will be used to send requests. This client only needs to be created
      // once, and can be reused for multiple requests.
      BigQuery bigquery = BigQueryOptions.getDefaultInstance().getService();

      BigtableColumnFamily.Builder statsSummary = BigtableColumnFamily.newBuilder();

      // Configuring Columns
      BigtableColumn connectedCell =
          BigtableColumn.newBuilder()
              .setQualifierEncoded(Base64.encodeBase64String("connected_cell".getBytes()))
              .setFieldName("connected_cell")
              .setType("STRING")
              .setEncoding("TEXT")
              .build();
      BigtableColumn connectedWifi =
          BigtableColumn.newBuilder()
              .setQualifierEncoded(Base64.encodeBase64String("connected_wifi".getBytes()))
              .setFieldName("connected_wifi")
              .setType("STRING")
              .setEncoding("TEXT")
              .build();
      BigtableColumn osBuild =
          BigtableColumn.newBuilder()
              .setQualifierEncoded(Base64.encodeBase64String("os_build".getBytes()))
              .setFieldName("os_build")
              .setType("STRING")
              .setEncoding("TEXT")
              .build();

      // Configuring column family and columns
      statsSummary
          .setColumns(ImmutableList.of(connectedCell, connectedWifi, osBuild))
          .setFamilyID("stats_summary")
          .setOnlyReadLatest(true)
          .setEncoding("TEXT")
          .setType("STRING")
          .build();

      // Configuring BigtableOptions is optional.
      BigtableOptions options =
          BigtableOptions.newBuilder()
              .setIgnoreUnspecifiedColumnFamilies(true)
              .setReadRowkeyAsString(true)
              .setColumnFamilies(ImmutableList.of(statsSummary.build()))
              .build();

      TableId tableId = TableId.of(datasetName, tableName);
      // Create a permanent table linked to the Bigtable table
      ExternalTableDefinition externalTable =
          ExternalTableDefinition.newBuilder(sourceUri, options).build();
      bigquery.create(TableInfo.of(tableId, externalTable));

      // Example query
      TableResult results = bigquery.query(QueryJobConfiguration.of(query));

      results
          .iterateAll()
          .forEach(row -> row.forEach(val -> System.out.printf("%s,", val.toString())));

      System.out.println("Query on external permanent table performed successfully.");
    } catch (BigQueryException | InterruptedException e) {
      System.out.println("Query not performed \n" + e.toString());
    }
  }
}

使用临时外部表查询 Cloud Bigtable 数据

如需在不创建永久表的情况下查询外部数据源,请运行命令进行以下组合:

  • 表定义文件与查询组合
  • 将内嵌架构定义与查询组合
  • 将 JSON 架构定义文件与查询组合

系统会使用表定义文件或提供的架构来创建临时外部表,然后对临时外部表运行查询。bq 命令行工具和 API 支持使用临时表查询外部数据源。

使用临时外部表时,并不会在您的某个 BigQuery 数据集中创建表。由于该表不会永久存储在数据集内,因此无法与他人共享。使用临时表查询外部数据源适用于对外部数据进行一次性临时查询,或执行提取、转换和加载 (ETL) 过程。

创建和查询临时外部表

如需使用临时外部表查询 Cloud Bigtable 数据,您需要执行以下操作:

bq 命令行工具和 API 目前支持创建和查询临时外部表。

bq

如需使用表定义文件查询临时表,请输入带 --external_table_definition 标志的 bq query 命令。

(可选)提供 --location 标志并将其值设置为您的位置

bq --location=location query \
--use_legacy_sql=false \
--external_table_definition=table::definition_file \
'query'

其中:

  • location位置的名称。--location 是可选标志。
  • table 是您要创建的临时表的名称。
  • definition_file 是本地机器上表定义文件的路径。
  • query 是您要提交到临时表的查询。

例如,以下命令使用名为 follows_def 的表定义文件创建并查询名为 follows 的临时表。

bq query \
--use_legacy_sql=false \
--external_table_definition=follows::/tmp/follows_def \
'SELECT
  COUNT(rowkey)
 FROM
   follows'

API

  • 创建查询。如需了解如何创建查询作业,请参阅查询数据

  • (可选)在作业资源jobReference 部分的 location 属性中指定您的位置。

  • 表资源设置 ExternalDataConfiguration 以指定外部数据源属性。

Java

试用此示例之前,请按照《BigQuery 快速入门:使用客户端库》中的 Java 设置说明进行操作。 如需了解详情,请参阅 BigQuery Java API 参考文档

import com.google.cloud.bigquery.BigQuery;
import com.google.cloud.bigquery.BigQueryException;
import com.google.cloud.bigquery.BigQueryOptions;
import com.google.cloud.bigquery.BigtableColumn;
import com.google.cloud.bigquery.BigtableColumnFamily;
import com.google.cloud.bigquery.BigtableOptions;
import com.google.cloud.bigquery.ExternalTableDefinition;
import com.google.cloud.bigquery.QueryJobConfiguration;
import com.google.cloud.bigquery.TableResult;
import com.google.common.collect.ImmutableList;
import org.apache.commons.codec.binary.Base64;

// Sample to queries an external bigtable data source using a temporary table
public class QueryExternalBigtableTemp {

  public static void main(String[] args) {
    // TODO(developer): Replace these variables before running the sample.
    String projectId = "MY_PROJECT_ID";
    String bigtableInstanceId = "MY_INSTANCE_ID";
    String bigtableTableName = "MY_BIGTABLE_NAME";
    String bigqueryTableName = "MY_TABLE_NAME";
    String sourceUri =
        String.format(
            "https://googleapis.com/bigtable/projects/%s/instances/%s/tables/%s",
            projectId, bigtableInstanceId, bigtableTableName);
    String query = String.format("SELECT * FROM %s ", bigqueryTableName);
    queryExternalBigtableTemp(bigqueryTableName, sourceUri, query);
  }

  public static void queryExternalBigtableTemp(String tableName, String sourceUri, String query) {
    try {
      // Initialize client that will be used to send requests. This client only needs to be created
      // once, and can be reused for multiple requests.
      BigQuery bigquery = BigQueryOptions.getDefaultInstance().getService();

      BigtableColumnFamily.Builder statsSummary = BigtableColumnFamily.newBuilder();

      // Configuring Columns
      BigtableColumn connectedCell =
          BigtableColumn.newBuilder()
              .setQualifierEncoded(Base64.encodeBase64String("connected_cell".getBytes()))
              .setFieldName("connected_cell")
              .setType("STRING")
              .setEncoding("TEXT")
              .build();
      BigtableColumn connectedWifi =
          BigtableColumn.newBuilder()
              .setQualifierEncoded(Base64.encodeBase64String("connected_wifi".getBytes()))
              .setFieldName("connected_wifi")
              .setType("STRING")
              .setEncoding("TEXT")
              .build();
      BigtableColumn osBuild =
          BigtableColumn.newBuilder()
              .setQualifierEncoded(Base64.encodeBase64String("os_build".getBytes()))
              .setFieldName("os_build")
              .setType("STRING")
              .setEncoding("TEXT")
              .build();

      // Configuring column family and columns
      statsSummary
          .setColumns(ImmutableList.of(connectedCell, connectedWifi, osBuild))
          .setFamilyID("stats_summary")
          .setOnlyReadLatest(true)
          .setEncoding("TEXT")
          .setType("STRING")
          .build();

      // Configuring BigtableOptions is optional.
      BigtableOptions options =
          BigtableOptions.newBuilder()
              .setIgnoreUnspecifiedColumnFamilies(true)
              .setReadRowkeyAsString(true)
              .setColumnFamilies(ImmutableList.of(statsSummary.build()))
              .build();

      // Configure the external data source and query job.
      ExternalTableDefinition externalTable =
          ExternalTableDefinition.newBuilder(sourceUri, options).build();
      QueryJobConfiguration queryConfig =
          QueryJobConfiguration.newBuilder(query)
              .addTableDefinition(tableName, externalTable)
              .build();

      // Example query
      TableResult results = bigquery.query(queryConfig);

      results
          .iterateAll()
          .forEach(row -> row.forEach(val -> System.out.printf("%s,", val.toString())));

      System.out.println("Query on external temporary table performed successfully.");
    } catch (BigQueryException | InterruptedException e) {
      System.out.println("Query not performed \n" + e.toString());
    }
  }
}

性能考虑因素

针对 Cloud Bigtable 外部数据源的查询性能取决于三个因素:

  • 行数
  • 读取的数据量
  • 并行化程度

BigQuery 会尝试仅读取查询中引用的列族,从而尽可能减少读取数据量。并行化程度取决于您在 Cloud Bigtable 集群中拥有的节点数以及您在自己的表中的拆分次数。

请注意,Cloud Bigtable 会根据负载自动合并拆分。如果读取该表的频率较低,则随着时间的推移,拆分次数会减少,查询性能会逐渐降低。如需详细了解如何按行键拆分表,请参阅管理表

通过 BigQuery 查询 Cloud Bigtable 会消耗 Cloud Bigtable CPU 周期。BigQuery 的 CPU 消耗可能会影响其他并发请求(例如实时用户流量传送)的延迟时间和吞吐量。例如,Cloud Bigtable 上的高 CPU 使用率会影响长尾查询,并增加第 99 百分位的延迟时间。

您应该监控 Cloud Bigtable 的 CPU 使用率,以验证此数据是否处于 Cloud Console 内 Cloud Bigtable 监控信息中心所注明的推荐范围内。通过增加实例的节点数,可更好地同时处理 BigQuery 流量和来自其他并发请求的流量。

生成的架构

默认情况下,BigQuery 将列族中的值作为列数组公开,并在其中包含以不同时间戳写入的值数组。该架构保留了 Cloud Bigtable 中数据的自然布局,但 SQL 查询可能难度较大。可能的解决方案是将列提升为父级列族中的子字段,并且只读取每个单元格中的最新值。这会以标量值的形式表示默认架构中的两个数组。

示例

您要存储某个虚构社交网络的用户个人资料。一个数据模型可能是一个 profile 列族,其中包含对应于 genderageemail 的各列:

rowkey | profile:gender| profile:age| profile:email
-------| --------------| -----------| -------------
alice  | female        | 30         | alice@gmail.com

使用默认架构时,计算 30 岁以上男性用户数量的标准 SQL 查询如下:

SELECT
  COUNT(1)
FROM
  `dataset.table`
OMIT
  RECORD IF NOT SOME(profile.column.name = "gender"
    AND profile.column.cell.value = "male")
  OR NOT SOME(profile.column.name = "age"
    AND INTEGER(profile.column.cell.value) > 30)

如果将 genderage 作为子字段公开提供,则查询数据会较为容易。为了将这些字段作为子字段公开提供,可以在定义表时将 genderage 作为 profile 列族中的命名列列出。您还可以指示 BigQuery 公开提供此列族中的最新值。通常情况下,我们只关注最新值(可能也是唯一的值)。

将列作为子字段公开提供之后,用于计算 30 岁以上男性用户数量的标准 SQL 查询如下:

SELECT
  COUNT(1)
FROM
  `dataset.table`
WHERE
  profile.gender.cell.value="male"
  AND profile.age.cell.value > 30

请注意如何将 genderage 直接作为字段引用。此设置的 JSON 配置如下:

  "bigtableOptions": {
    "readRowkeyAsString": "true",
    "columnFamilies": [
      {
          "familyId": "profile",
          "onlyReadLatest": "true",
          "columns": [
              {
                  "qualifierString": "gender",
                  "type": "STRING"
              },
              {
                  "qualifierString": "age",
                  "type": "INTEGER"
              }
          ]
      }
    ]
  }

值编码

Cloud Bigtable 将数据存储为与数据编码无关的原始字节。但字节值在 SQL 查询分析中的用途有限。Cloud Bigtable 提供两种基本类型的标量解码:文本和 HBase 二进制。

文本格式假定所有值均存储为字母数字文本字符串。例如,整数 768 将存储为字符串“768”。二进制编码假定使用 HBase 的 Bytes.toBytes 方法类对数据进行编码,并应用适当的解码方法。

查询过滤器

采用行相等性过滤器的查询仅读取该特定行。例如,在以下标准 SQL 语法中:

SELECT
  COUNT(follows.column.name)
FROM
  `dataset.table`
WHERE
  rowkey = "alice";

系统也支持 rowkey > '1'rowkey < '8' 等范围过滤条件,但只有在使用 readRowkeyAsString 选项以字符串形式读取 rowkey 时才能提供此支持。