1. はじめに
この Codelab では、Cloud Bigtable と Java HBase クライアントの使用方法について学びます。
方法を学ぶ対象
- スキーマ設計でよくあるミスを回避する
- シーケンス ファイルでデータをインポートする
- データに対するクエリを実行する
完了すると、ニューヨーク市のバスのデータを示す地図がいくつか作成されます。たとえば、マンハッタンでのバス賃走のヒートマップを作成します。
Google Cloud Platform の使用経験をどのように評価されますか。
このチュートリアルの利用方法をお選びください。
<ph type="x-smartling-placeholder">2. データセットについて
ニューヨーク市のバスに関するデータセットを見てみます。300 以上のバスルートがあり、これらのルートをたどる車両は 5,800 台以上あります。作成したデータセットは、目的地の名前、車両 ID、緯度、経度、到着予定時刻、到着予定時刻を含むログです。このデータセットは、2017 年 6 月に約 10 分ごとに取得されたスナップショットで構成されています。
3. スキーマの設計
Cloud Bigtable のパフォーマンスを最大限に高めるには、スキーマの設計を慎重に検討する必要があります。Cloud Bigtable のデータは、辞書順で自動的に並べ替えられるため、スキーマの設計がうまくいけば、関連データのクエリは非常に効率的になります。Cloud Bigtable では、行キーによるポイント ルックアップや行範囲スキャンを使用したクエリで、連続する行のセットを返すことができます。ただし、スキーマが十分に検討されていないと、複数の行のルックアップをつなぎ合わせたり、最悪な場合、テーブル全体のスキャンを行ったりすることになり、オペレーションが非常に低速になります。
クエリを計画する
データにはさまざまな情報が含まれていますが、この Codelab ではバスの場所と目的地を使用します。
この情報を使用して、次のクエリを実行できます。
- 指定された 1 時間に 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 をアクティブにする] をクリックすると、ボタンをタップします。
次の環境変数を設定して、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. データをインポートする
以下の手順に沿って、gs://cloud-bigtable-public-datasets/bus-data
から、この Codelab の一連のシーケンス ファイルをインポートします。
次のコマンドを実行して、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 日の午前 12 時から午前 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));
結果には、その 1 時間におけるバスの最新の位置情報が含まれます。ただし、すべてのロケーションを確認する必要がある場合は、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 で次のコマンドを実行して、ある時間におけるそのバスの緯度と経度のリストを取得します。
mvn package exec:java -Dbigtable.projectID=$GOOGLE_CLOUD_PROJECT \ -Dbigtable.instanceID=$INSTANCE_ID -Dbigtable.table=$TABLE_ID \ -Dquery=lookupVehicleInGivenHour
緯度と経度をコピーして MapMaker App に貼り付け、結果を可視化できます。何度か終わると、無料アカウントの作成を求めるメッセージが表示されます。アカウントを作成するか、既存のレイヤを削除することができます。この Codelab には、ステップごとの可視化機能が含まれています。最初のクエリの結果は次のとおりです。
8. スキャンを実行する
次に、その時間のバス路線のすべてのデータを表示しましょう。スキャンコードは get コードによく似ています。スキャナに開始位置を指定してから、タイムスタンプ 1496275200000 で表される 1 時間以内の 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
マップメーカー アプリでは一度に複数のリストを表示できるため、最初に実行したクエリでどのバスが車両であるかを確認できます。
このクエリの興味深い修正として、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 にコピーすると、バス路線のヒートマップを表示できます。オレンジ色の点は停車地を示し、明るい赤色の点は経路の始点と終点を示します。
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
東に向かうバス
西に向かうバス
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
11. 完了
料金が発生しないようにクリーンアップする
この Codelab で使用したリソースについて、Google Cloud Platform アカウントに課金されないようにするには、インスタンスを削除する必要があります。
gcloud bigtable instances delete $INSTANCE_ID
学習した内容
- スキーマの設計
- インスタンス、テーブル、ファミリーの設定
- Dataflow を使用したシーケンス ファイルのインポート
- ルックアップ、スキャン、フィルタを使用したスキャン、マルチ範囲スキャンを使用したクエリ
次のステップ
- Cloud Bigtable の詳細については、ドキュメントをご覧ください。
- GCP のその他の機能を試すには、チュートリアルをご覧ください。
- OpenTSDB インテグレーションを使用して時系列データをモニタリングする方法を学習する