Cloud Bigtable データのクエリ

このページでは、BigQuery を使用して、Cloud Bigtable に保存されたデータをクエリする方法について説明します。

Cloud Bigtable は Google の分散型 NoSQL データベースで、数十億行、数千列規模に拡張し、数ペタバイトのデータを格納できます。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 は 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 インスタンスを作成するときに、インスタンスに対するスコープのリストを指定できます。このスコープは、Cloud Bigtable を含む、Google Cloud プロダクトに対するインスタンスのアクセスを制御します。VM で実行されるアプリケーションは、サービス アカウントを使用して 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 データセット内にはテーブルを作成しません。テーブルはデータセットに永続的に保存されないため、このテーブルを他のユーザーと共有することはできません。一時テーブルを使用して外部データソースにクエリを実行する方法は、外部データに 1 回限りのアドホック クエリを実行する場合、あるいは抽出、変換、読み込み(ETL)プロセスを行う場合に便利です。

外部の永続テーブルを使用して Cloud Bigtable データをクエリする

永続テーブルを使用して Cloud Bigtable のデータソースに対してクエリを実行するには、Cloud Bigtable データソースにリンクするテーブルを BigQuery データセットに作成します。データは BigQuery テーブルに保存されません。テーブルが永続的であるため、データセット レベルのアクセス制御を使用すると、基礎となる Cloud Bigtable データソースへのアクセスが可能な他のユーザーとテーブルを共有できます。

Cloud Bigtable データソースにリンクされる外部の永続テーブルを BigQuery で作成する場合、テーブルのスキーマは次の 2 つの方法で指定できます。

  • API または bq コマンドライン ツールを使用している場合、外部データソースのスキーマとメタデータを定義するテーブル定義ファイルを作成します。
  • Cloud Console を使用している場合は、Cloud Bigtable の列ファミリーと列修飾子を手動で入力します。

外部の永続テーブルを使用して Cloud Bigtable データをクエリするには、次の操作を行います。

  • テーブル定義ファイルを作成する(API または bq コマンドライン ツールの場合)
  • BigQuery に外部データソースにリンクするテーブルを作成する
  • 永続テーブルを使用してデータに対してクエリを実行する

外部の永続テーブルを作成してクエリを実行する

永続テーブルを作成してクエリを実行するには:

Console

現時点では、Cloud Console を使用して Cloud Bigtable データに対するクエリを実行することはできません。

bq

bq mk コマンドを使用して bq コマンドライン ツールでテーブルを作成します。外部データソースにリンクされるテーブルを bq コマンドライン ツールを使用して作成するときは、テーブル定義ファイルを使用してテーブルのスキーマを指定します。

  1. 永続テーブルの作成には、bq mk コマンドを使用します。

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

    ここで

    • definition_file は、ローカルマシン上のテーブル定義ファイルのパスです。
    • dataset は、テーブルを含むデータセットの名前です。
    • table は、作成するテーブルの名前です。

    これで、ネイティブ BigQuery テーブルの場合と同じようにテーブルに対してクエリを実行できます。ただし、外部データソースの制限は適用されます。

API

  • テーブル リソースsourceUris プロパティに指定できる Cloud Bigtable URI は 1 つだけです。これは、完全指定の有効な HTTPS URL でなければなりません。

  • 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 データに対してクエリを実行する

永続テーブルを作成せずに外部データソースに対してクエリを行うには、以下を結合するコマンドを実行します。

テーブル定義ファイルまたは指定したスキーマを使用して一時外部テーブルが作成され、そのテーブルに対してクエリが実行されます。一時テーブルを使用した外部データソースに対するクエリの実行は、bq コマンドライン ツールと API でサポートされています。

外部の一時テーブルを使用する場合は、BigQuery データセット内にテーブルが作成されません。テーブルはデータセットに永続的に保存されないため、このテーブルを他のユーザーと共有することはできません。外部データに対する 1 回限りのアドホック クエリを行う場合、または抽出、変換、読み込み(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 外部データソースに対するクエリのパフォーマンスは、次の 3 つの要素の影響を受けます。

  • 行数
  • 読み取るデータの量
  • 同時読み込みの範囲

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 クエリでは操作が難しくなる場合があります。親の列ファミリー内で列がサブフィールドになる場合や、各セルから最新の値だけが読み取られる場合があります。これにより、デフォルト スキーマでは、両方の配列がスカラー値として扱われます。

架空のソーシャル ネットワークにユーザー プロフィールを保存しています。このデータモデルの 1 つは 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 がサブフィールドとして公開されていれば、クエリはそれほど難しいものではありません。これらをサブフィールドとして公開するには、テーブルを定義するときに、profile 列ファミリーで genderage を名前付きの列として設定します。また、この列ファミリーから最新の値を公開するように 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 はデータを RAW バイトとして保存し、データのエンコードに依存しません。ただし、SQL クエリ分析でバイト値を使用する場合に制限があります。Cloud Bigtable には、2 種類の基本的なスカラー デコード(テキストと HBase バイナリ)があります。

テキスト形式は、すべての値が英数字のテキスト文字列で保存されていることを前提としています。たとえば、整数 768 は文字列 "768" として保存されます。バイナリ エンコードでは、データのエンコードに HBase の Bytes.toBytes メソッド群が使用されたと想定し、適切なデコード方法が適用されます。

クエリフィルタ

行のクエリは、特定の行のみを読み込むフィルタと同じ機能になります。たとえば、次の標準 SQL 構文では、

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

rowkey > '1'rowkey < '8' などの範囲フィルタも使用できます。ただし、使用できるのは、readRowkeyAsString オプションで rowkey が文字列として読み取られる場合に限ります。