Cloud Bigtable の概要

1. はじめに

この Codelab では、Java HBase クライアントCloud Bigtable を使用する方法について説明します。

方法を学ぶ対象

  • スキーマ設計でよくある間違いを回避する
  • シーケンス ファイルのデータをインポートする
  • データに対するクエリを実行する

完了すると、ニューヨーク市のバスのデータを示す地図が複数作成されます。たとえば、マンハッタンのバスの移動に関する次のヒートマップを作成します。

7349d94f7d41f1d1.png

Google Cloud Platform のご利用経験についてどのように評価されますか?

初心者 中級者 上級者

このチュートリアルの利用方法をお選びください。

通読のみ 通読して演習を行う

2. データセットについて

ニューヨーク市のバスに関するデータセットを見てみましょう。300 以上のバス路線があり、5,800 台の車両がこれらの路線を運行しています。データセットは、目的地名、車両 ID、緯度、経度、到着予定時刻、到着予定時刻を含むログです。このデータセットは、2017 年 6 月に約 10 分ごとに取得されたスナップショットで構成されています。

3. スキーマの設計

Cloud Bigtable で最高のパフォーマンスを得るには、スキーマを設計する際に慎重に検討する必要があります。Cloud Bigtable のデータは辞書順に自動的に並べ替えられるため、スキーマを適切に設計すれば、関連データのクエリを非常に効率的に実行できます。Cloud Bigtable では、行キーによるポイント ルックアップまたは連続した行のセットを返す行範囲スキャンを使用するクエリが可能です。ただし、スキーマが十分に検討されていないと、複数の行ルックアップを組み合わせたり、最悪の場合、非常に遅いオペレーションであるテーブルのフルスキャンを実行したりすることになります。

クエリを計画する

データにはさまざまな情報が含まれていますが、この Codelab では、バスの現在地目的地を使用します。

この情報を使用して、次のクエリを実行できます。

  • 指定した時間帯の 1 台のバスの位置を取得します。
  • バス路線または特定のバスの 1 日分のデータを取得します。
  • 地図上の長方形内のすべてのバスを検索します。
  • すべてのバスの現在地を取得します(このデータをリアルタイムで取り込んでいる場合)。

このクエリのセットをすべて同時に最適に実行することはできません。たとえば、時間で並べ替える場合、テーブル全体のスキャンを実行せずに場所に基づいてスキャンすることはできません。最も頻繁に実行するクエリに基づいて優先順位を設定する必要があります。

この Codelab では、次のクエリセットの最適化と実行に焦点を当てます。

  • 特定の車両の 1 時間以上の位置情報を取得します。
  • 1 時間以上にわたるバス路線の全区間の位置情報を取得します。
  • マンハッタンのすべてのバスの位置を 1 時間以内に取得します。
  • マンハッタンのすべてのバスの最新の位置情報を 1 時間以内に取得します。
  • 1 か月間のバス路線の全区間の位置情報を取得します。
  • 特定の目的地に向かうバス路線の 1 時間以上の位置情報を取得します。

行キーを設計する

この Codelab では静的データセットを使用しますが、スケーラビリティを考慮したスキーマを設計します。テーブルにバスデータをさらにストリーミングしても、パフォーマンスを維持できるスキーマを設計します。

行キーのスキーマ案は次のとおりです。

[バス会社/バス路線/タイムスタンプ(時間単位で切り捨て) / 車両 ID]。各行には 1 時間分のデータが含まれ、各セルにはデータのタイムスタンプ付きの複数のバージョンが格納されます。

この Codelab では、わかりやすくするために 1 つの列ファミリーを使用します。データの表示例を次に示します。データは行キーで並べ替えられます。

行キー

cf:VehicleLocation.Latitude

cf:VehicleLocation.Longitude

...

MTA/M86-SBS/1496275200000/NYCT_5824

40.781212 @20:52:54.0040.776163 @20:43:19.0040.778714 @20:33:46.00

-73.961942 @20:52:54.00-73.946949 @20:43:19.00-73.953731 @20:33:46.00

...

MTA/M86-SBS/1496275200000/NYCT_5840

40.780664 @20:13:51.0040.788416 @20:03:40.00

-73.958357 @20:13:51.00 -73.976748 @20:03:40.00

...

MTA/M86-SBS/1496275200000/NYCT_5867

40.780281 @20:51:45.0040.779961 @20:43:15.0040.788416 @20:33:44.00

-73.946890 @20:51:45.00-73.959465 @20:43:15.00-73.976748 @20:33:44.00

...

...

...

...

...

4. インスタンス、テーブル、ファミリーを作成する

次に、Cloud Bigtable テーブルを作成します。

まず、新しいプロジェクトを作成します。右上隅にある [Cloud Shell をアクティブにする] ボタンをクリックして開くことができる、組み込みの Cloud Shell を使用します。

a74d156ca7862b28.png

次の環境変数を設定して、Codelab コマンドのコピーと貼り付けを簡単にします。

INSTANCE_ID="bus-instance"
CLUSTER_ID="bus-cluster"
TABLE_ID="bus-data"
CLUSTER_NUM_NODES=3
CLUSTER_ZONE="us-central1-c"

Cloud Shell には、この Codelab で使用するツール(gcloud コマンドライン ツールcbt コマンドライン インターフェースMaven)がすでにインストールされています。

次のコマンドを実行して、Cloud Bigtable API を有効にします。

gcloud services enable bigtable.googleapis.com bigtableadmin.googleapis.com

次のコマンドを実行して、インスタンスを作成します。

gcloud bigtable instances create $INSTANCE_ID \
    --cluster=$CLUSTER_ID \
    --cluster-zone=$CLUSTER_ZONE \
    --cluster-num-nodes=$CLUSTER_NUM_NODES \
    --display-name=$INSTANCE_ID

インスタンスを作成したら、cbt 構成ファイルにデータを入力し、次のコマンドを実行してテーブルと列ファミリーを作成します。

echo project = $GOOGLE_CLOUD_PROJECT > ~/.cbtrc
echo instance = $INSTANCE_ID >> ~/.cbtrc

cbt createtable $TABLE_ID
cbt createfamily $TABLE_ID cf

5. データをインポートする

次の手順で、この Codelab の一連のシーケンス ファイルをインポートします。gs://cloud-bigtable-public-datasets/bus-data

次のコマンドを実行して、Cloud Dataflow API を有効にします。

gcloud services enable dataflow.googleapis.com

次のコマンドを実行してテーブルをインポートします。

NUM_WORKERS=$(expr 3 \* $CLUSTER_NUM_NODES)
gcloud beta dataflow jobs run import-bus-data-$(date +%s) \
--gcs-location gs://dataflow-templates/latest/GCS_SequenceFile_to_Cloud_Bigtable \
--num-workers=$NUM_WORKERS --max-workers=$NUM_WORKERS \
--parameters bigtableProject=$GOOGLE_CLOUD_PROJECT,bigtableInstanceId=$INSTANCE_ID,bigtableTableId=$TABLE_ID,sourcePattern=gs://cloud-bigtable-public-datasets/bus-data/*

インポートをモニタリングする

ジョブは Cloud Dataflow UI でモニタリングできます。また、Cloud Bigtable インスタンスのモニタリング UI で負荷を確認することもできます。インポート全体には 5 分ほどかかります。

6. コードを取得する

git clone https://github.com/googlecodelabs/cbt-intro-java.git
cd cbt-intro-java

次のコマンドを実行して、Java 11 に変更します。

sudo update-java-alternatives -s java-1.11.0-openjdk-amd64 && export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64/

7. ルックアップを実行する

最初に実行するクエリは、シンプルな行ルックアップです。2017 年 6 月 1 日午前 0 時から午前 1 時までの M86-SBS 線のバスのデータが取得されます。この時間帯には、ID が NYCT_5824 の車両がバス路線を走行しています。

この情報と、スキーマ設計(バス会社/バス路線/時間単位に切り捨てられたタイムスタンプ/車両 ID)から、行キーは次のようになります。

MTA/M86-SBS/1496275200000/NYCT_5824

BusQueries.java

private static final byte[] COLUMN_FAMILY_NAME = Bytes.toBytes("cf");
private static final byte[] LAT_COLUMN_NAME = Bytes.toBytes("VehicleLocation.Latitude");
private static final byte[] LONG_COLUMN_NAME = Bytes.toBytes("VehicleLocation.Longitude");

String rowKey = "MTA/M86-SBS/1496275200000/NYCT_5824";
Result getResult =
    table.get(
        new Get(Bytes.toBytes(rowKey))
            .addColumn(COLUMN_FAMILY_NAME, LAT_COLUMN_NAME)
            .addColumn(COLUMN_FAMILY_NAME, LONG_COLUMN_NAME));

結果には、その時間内のバスの最新の位置が含まれている必要があります。すべてのロケーションを表示するには、GET リクエストでバージョンの最大数を設定します。

BusQueries.java

Result getResult =
    table.get(
        new Get(Bytes.toBytes(rowKey))
            .setMaxVersions(Integer.MAX_VALUE)
            .addColumn(COLUMN_FAMILY_NAME, LAT_COLUMN_NAME)
            .addColumn(COLUMN_FAMILY_NAME, LONG_COLUMN_NAME));

Cloud Shell で次のコマンドを実行して、そのバスの 1 時間の緯度と経度のリストを取得します。

mvn package exec:java -Dbigtable.projectID=$GOOGLE_CLOUD_PROJECT \
-Dbigtable.instanceID=$INSTANCE_ID -Dbigtable.table=$TABLE_ID \
-Dquery=lookupVehicleInGivenHour

緯度と経度をコピーして MapMaker アプリに貼り付けると、結果を可視化できます。数レイヤを完了すると、無料アカウントの作成を求めるメッセージが表示されます。アカウントを作成するか、既存のレイヤを削除するだけです。この Codelab には、各ステップの可視化が含まれています。最初のクエリの結果は次のとおりです。

f1a1fac6051c6210.png

8. スキャンを実行する

次に、その時間のバス路線のすべてのデータを表示します。スキャンコードは取得コードとよく似ています。スキャナに開始位置を指定し、タイムスタンプ 1496275200000 で示された時間内の M86-SBS バス路線の行のみを必要とすることを指定します。

BusQueries.java

Scan scan;
scan = new Scan();
scan.setMaxVersions(Integer.MAX_VALUE)
    .addColumn(COLUMN_FAMILY_NAME, LAT_COLUMN_NAME)
    .addColumn(COLUMN_FAMILY_NAME, LONG_COLUMN_NAME)
    .withStartRow(Bytes.toBytes("MTA/M86-SBS/1496275200000"))
    .setRowPrefixFilter(Bytes.toBytes("MTA/M86-SBS/1496275200000"));
ResultScanner scanner = table.getScanner(scan);

次のコマンドを実行して結果を取得します。

mvn package exec:java -Dbigtable.projectID=$GOOGLE_CLOUD_PROJECT \
-Dbigtable.instanceID=$INSTANCE_ID -Dbigtable.table=$TABLE_ID \
-Dquery=scanBusLineInGivenHour

c18a4ac6522d08a2.png

Map Maker アプリでは複数のリストを同時に表示できるため、実行した最初のクエリの車両がどのバスであるかを確認できます。

234c1b51e3b201e.png

このクエリの興味深い変更は、M86-SBS バス路線の 1 か月分のデータを表示することです。これは非常に簡単に行うことができます。開始行とプレフィックス フィルタからタイムスタンプを削除して、結果を取得します。

BusQueries.java

scan.withStartRow(Bytes.toBytes("MTA/M86-SBS/"))
    .setRowPrefixFilter(Bytes.toBytes("MTA/M86-SBS/"));

// Optionally, reduce the results to receive one version per column
// since there are so many data points.
scan.setMaxVersions(1);

次のコマンドを実行して結果を取得します。(結果の長いリストが表示されます)。

mvn package exec:java -Dbigtable.projectID=$GOOGLE_CLOUD_PROJECT \
-Dbigtable.instanceID=$INSTANCE_ID -Dbigtable.table=$TABLE_ID \
-Dquery=scanEntireBusLine

結果を MapMaker にコピーすると、バス路線のヒートマップを表示できます。オレンジ色のブロブは停車地を示し、明るい赤色のブロブはルートの始点と終点を示します。

346f52e61b3d8902.png

9. フィルタの導入

次に、東行きのバスと西行きのバスでフィルタリングし、それぞれに個別のヒートマップを作成します。

BusQueries.java

Scan scan;
ResultScanner scanner;
scan = new Scan();
SingleColumnValueFilter valueFilter =
    new SingleColumnValueFilter(
        COLUMN_FAMILY_NAME,
        Bytes.toBytes("DestinationName"),
        CompareOp.EQUAL,
        Bytes.toBytes("Select Bus Service Yorkville East End AV"));

scan.setMaxVersions(1)
    .addColumn(COLUMN_FAMILY_NAME, LAT_COLUMN_NAME)
    .addColumn(COLUMN_FAMILY_NAME, LONG_COLUMN_NAME);
scan.withStartRow(Bytes.toBytes("MTA/M86-SBS/"))
    .setRowPrefixFilter(Bytes.toBytes("MTA/M86-SBS/"));
scan.setFilter(valueFilter);
scanner = table.getScanner(scan);

次のコマンドを実行して、東行きのバスの結果を取得します。

mvn package exec:java -Dbigtable.projectID=$GOOGLE_CLOUD_PROJECT \
-Dbigtable.instanceID=$INSTANCE_ID -Dbigtable.table=$TABLE_ID \
-Dquery=filterBusesGoingEast

西行きのバスを取得するには、valueFilter の文字列を変更します。

BusQueries.java

SingleColumnValueFilter valueFilter =
    new SingleColumnValueFilter(
        COLUMN_FAMILY_NAME,
        Bytes.toBytes("DestinationName"),
        CompareOp.EQUAL,
        Bytes.toBytes("Select Bus Service Westside West End AV"));

次のコマンドを実行して、西行きのバスの結果を取得します。

mvn package exec:java -Dbigtable.projectID=$GOOGLE_CLOUD_PROJECT \
-Dbigtable.instanceID=$INSTANCE_ID -Dbigtable.table=$TABLE_ID \
-Dquery=filterBusesGoingWest

東に向かうバス

76f6f62096a6847a.png

西に向かうバス

2b5771ee9046399f.png

2 つのヒートマップを比較すると、ルートの違いだけでなく、ペースの違いも確認できます。このデータから、西行きのルートでは、特にセントラル パークに入る際にバスが停車する回数が多いことがわかります。東行きのバスでは、チョークポイントはあまり見られません。

10. マルチレンジ スキャンを実行する

最後のクエリでは、あるエリアの多くのバス路線に関心がある場合に対処します。

BusQueries.java

private static final String[] MANHATTAN_BUS_LINES = {"M1","M2","M3",...

Scan scan;
ResultScanner scanner;
List<RowRange> ranges = new ArrayList<>();
for (String busLine : MANHATTAN_BUS_LINES) {
  ranges.add(
      new RowRange(
          Bytes.toBytes("MTA/" + busLine + "/1496275200000"), true,
          Bytes.toBytes("MTA/" + busLine + "/1496275200001"), false));
}
Filter filter = new MultiRowRangeFilter(ranges);
scan = new Scan();
scan.setFilter(filter);
scan.setMaxVersions(Integer.MAX_VALUE)
    .addColumn(COLUMN_FAMILY_NAME, LAT_COLUMN_NAME)
    .addColumn(COLUMN_FAMILY_NAME, LONG_COLUMN_NAME);
scan.withStartRow(Bytes.toBytes("MTA/M")).setRowPrefixFilter(Bytes.toBytes("MTA/M"));
scanner = table.getScanner(scan);

次のコマンドを実行して結果を取得します。

mvn package exec:java -Dbigtable.projectID=$GOOGLE_CLOUD_PROJECT \
-Dbigtable.instanceID=$INSTANCE_ID -Dbigtable.table=$TABLE_ID \
-Dquery=scanManhattanBusesInGivenHour

7349d94f7d41f1d1.png

11. 完了する

課金が発生しないようにクリーンアップする

この Codelab で使用したリソースについて、Google Cloud Platform アカウントに課金されないようにするには、インスタンスを削除します。

gcloud bigtable instances delete $INSTANCE_ID

学習した内容

  • スキーマの設計
  • インスタンス、テーブル、ファミリーの設定
  • Dataflow を使用したシーケンス ファイルのインポート
  • ルックアップ、スキャン、フィルタ付きスキャン、マルチレンジ スキャンを使用したクエリ

次のステップ