時系列データ用のスキーマ設計

このページでは、Cloud Bigtable で時系列データを格納する場合のスキーマ設計のコンセプト、パターン、例について説明します。このページを読む前に、Cloud Bigtable の概要を理解する必要があります。スキーマ設計にについても十分に理解している必要があります。

何かを測定して、その測定結果と一緒に時刻を記録する際は、常に時系列を構築します。時系列は至るところにあります。

  • パソコンの動作が遅くなっているためにメモリ使用率のプロットを見るときは、時系列で見ています。

  • ニュースで時間帯ごとの気温を見るときは、時系列で見ています。

  • 外国為替トレーダーが業務で USD/JPY の 5 日、10 日、30 日の移動平均相場をプロットするときは、時系列で見ています。

時系列は非常に重要です。

  • 時系列は、リソース使用率の最適化、電力使用量の軽減、環境影響の最小化、コスト削減に役立ちます。

  • 時系列は、過去に何が起こったかを具体的に示し、将来何が起こるかについて情報に基づいた見積もりを行うための、データの傾向の特定に役立ちます。

  • 時系列は、金融サービス、小売、保険、物理学、化学などの分野で一部の複雑な解析や機械学習を支えています。

このガイドでは、時系列データを Cloud Bigtable に保存してクエリを実行するための方法について説明し、チュートリアルを示します。

時系列と Cloud Bigtable

時系列データを Cloud Bigtable に保存することは無理なく適合します。Cloud Bigtable ではデータが非構造化列として行に保存され、各行には行キーがあり、行キーは辞書順で並べ替えられています。

Cloud Bigtable からデータを取得する際によく使用される 2 つの方法があります。

  • 行キーを指定して 1 行を取得できます。
  • 行キーの範囲を指定して複数行を取得できます。

指定した時間範囲のデータ(たとえば、その日のすべての市場データや、直近 15 分間のサーバー CPU の統計情報)が必要なことはよくあるため、これらの方法は時系列データのクエリを行うには最適です。そのため、Cloud Bigtable は機能上、時系列データに最も適しています。

もちろん、詳細を検討すると問題が出てきます。Cloud Bigtable では、データのスキーマ(列と行キーの構造)を慎重に設計する必要があることがその問題点です。スキーマが適切であれば、高いパフォーマンスと拡張性を得られますが、スキーマが不適切であればパフォーマンスが悪いシステムになることがあります。しかし、すべてのユースケースに最適な 1 つのスキーマ設計があるわけではありません。

このページの残りの部分では、Cloud Bigtable でのスキーマ設計のいくつかのパターンを示しています。これらのパターンを使用して、各自のユースケースに最適なスキーマを設計できます。スキーマ設計のパターンを列挙して説明した後に、以下のユースケースの例について説明しています。

  • 金融市場データ
  • サーバーの指標(CPU、メモリ、ネットワーク使用率、など)
  • インテリジェント電力メーター(「モノのインターネット」(IoT)の一部)

時系列のスキーマ設計パターン

Cloud Bigtable に時系列の保存するためのスキーマ設計パターンは次のカテゴリに当てはめることができます。

  • 一般的なパターン
  • 行キー設計のパターン
  • データ列設計のパターン

一般的なパターン

短くて意味のある名前にする

Cloud Bigtable からデータを転送する際に、次の項目を含むメタデータも転送されます。

  • 行キー
  • 関連する列をグループ化するために使用される識別子である列ファミリー
  • 列ファミリー内でユニークな名前である列修飾子

その結果、それぞれの名前のサイズはストレージと RPC のオーバーヘッドが増大する一因となるため、できるだけ短くて意味のある名前を選ぶことが重要です。たとえば、列修飾子として CELLPHONE_NUMBER を使用するのではなく、短くて意味のある省略形として CELL を使用します。

行キー設計のパターン

縦長のテーブルを使用する

縦長のテーブルでは行あたりのイベント数が少なく、1 つだけのこともあります。一方、横長のテーブルでは、行あたりに多数のイベント数があります。この後に説明しますが、縦長のテーブルは時系列データに最適です。

たとえば、菜園の温度を毎朝測ることを考えます。そう決めたら、毎朝温度を測るのだから 1 日ごとに 1 行とするのが適切であるため、テーブルは縦長です。次の例で、行キーの最初の要素がタイムスタンプではないことに注意します。後で説明しますが、行キーの最初の要素をタイムスタンプにすると、さまざまな問題が起こります。

行キー 列データ
VEGGIEGARDEN#20150301 DAILY:TEMP:60.4
VEGGIEGARDEN#20150302 DAILY:TEMP:61.2
VEGGIEGARDEN#20150303 DAILY:TEMP:61.0
VEGGIEGARDEN#20150304 DAILY:TEMP:65.1
VEGGIEGARDEN#20150305 DAILY:TEMP:62.2
... ...
VEGGIEGARDEN#20150331 DAILY:TEMP:60.4

一方、各月の温度をプロットすることを考えると、月ごとに 1 行が適切です。次の例では、その結果の横長のテーブルを示しています。

行キー 列データ
VEGGIEGARDEN#20150301 TEMP:1:60.4 TEMP:2:61.2 TEMP:3:61.0 TEMP:4:65.1 TEMP:5:62.2 ... TEMP:31:60.4

時系列では、通常は縦長のテーブルを使用します。 それには 2 つの理由があります。1 つ目は、1 行に 1 つのイベントを保存すると、データに対するクエリの実行が簡単になることです。2 つ目は、1 行に多数のイベントを保存すると、行の合計サイズが推奨最大値を超える可能性が高くなることです(行は大きくできるが無制限ではないを参照)。

最適化のために横長のテーブルを使用することはできますが、無限個のイベントを保存することは避けてください。 たとえば、ある月のイベント全体を一度に取得する必要があることが多い場合には、行のサイズが 1 か月の日数に限定されているため、上記の横長の温度テーブルは合理的な最適化です。

列バージョンより行を優先する

Cloud Bigtable では、列にタイムスタンプ付きのバージョンを持たせることができます。そのため、時系列を列の一連のバージョンとして保存することが理論上は可能です。たとえば、ZXZZT 株の各日の終値を記録する場合に、各日のタイムスタンプ付きのバージョンを持つ 1 つの列にすることができます。

行キー 列データ
ZXZZT STOCK:PRICE (V1 03/01/15):558.40 STOCK:PRICE (V2 03/02/15):571.34 STOCK:PRICE (V3 03/03/15):573.64 STOCK:PRICE (V4 03/04/15):573.37 STOCK:PRICE (V5 03/05/15):575.33

しかし、これはこのようなデータを保存するための最適な方法ではありません。

デフォルトでは、列のバージョンではなく新しい行を使用します。 各行の 1 つイベントの 1 つのバージョンを持つ複数の行を使用するのが、データを表現、理解、クエリするための最も単純な方法です。

実際に値を改正し、その履歴が重要であるユースケースでは、列のバージョンを使用することを許容できます。 たとえば、ZXZZT の終値に基づいて一連の計算を行い、終値の 558.40 を最初に間違って 559.40 と入力したとします。この場合に、その間違った値によって他の誤計算が引き起こされていれば、その値の履歴を知ることが重要である可能性があります。

クエリを考慮して行キーを設計する

Cloud Bigtable では、行を保存する際に行キーの辞書順で行が並べ替えられます。実際にテーブルごとに 1 つのインデックスがあり、それは行キーです。1 つの行または行の範囲にアクセスするクエリは迅速かつ効率的に実行されます。その他のすべてのスキャンではテーブルが完全スキャンされることになり、はるかに遅くなります。テーブルの完全スキャンは、まさにその名前のとおり、テーブルのすべての行が 1 つずつ順に調べられます。Cloud Bigtable では、1 つのテーブルに数ペタバイトのデータを保存できますが、システムが大きくなるにつれて、テーブルの完全スキャンのパフォーマンスはどんどん低下してしまいします。

たとえば、ビデオゲームからプレーヤーのスコアを保存するテーブルを考えてみると、そのテーブルは次のように設計できます。

行キー 列データ
LoL#20150301 GAME:PLAYER:Corrie GAME:WIN:false GAME:KDA:4.25
LoL#20150302 GAME:PLAYER:Jo GAME:WIN:true GAME:KDA:7.00
LoL#20150302 GAME:PLAYER:Sam GAME:WIN:true GAME:KDA:7.00
LoL#20150303 GAME:PLAYER:Corrie GAME:WIN:true GAME:KDA:9.50
Starcraft#20150303 GAME:PLAYER:Eriko GAME:WIN:true GAME:KDA:6.00

「3 月に Corrie がゲームで獲得した LoL はいくつですか?」という質問に回答するために、このデータにクエリを実行するとします。上記に示したスキーマでは、この質問に回答するためにテーブルのほとんどのデータをスキャンする必要があります。一方、このテーブルを次のように設計していれば、特定の行範囲を取得することによってこのクエリを完了できます。

行キー 列データ
LoL#Corrie#20150301 GAME:WIN:false GAME:KDA:4.25
LoL#Corrie#20150303 GAME:WIN:true GAME:KDA:9.50
LoL#Jo#20150302 GAME:WIN:true GAME:KDA:7.00
LoL#Sam#20150302 GAME:WIN:true GAME:KDA:7.00
Starcraft#Eriko#20150303 GAME:WIN:true GAME:KDA:6.00

システム全体で良好なパフォーマンスを得るには、よく使用するクエリを容易にする行キーを選ぶことが最も重要です。 クエリを列挙して重要度の順に並べ、それらのクエリに都合のいい行キーを設計します。

最適な行キーがない場合にはどうしますか?たとえば、3 月のすべての LoL ゲームについてのクエリと、3 月に Corrie がプレイしたすべての LoL ゲームについてクエリが同じくらい重要だとします。上記のスキーマでは、3 月の Corrie の LoL ゲームについてのクエリを実行できますが、3 月のすべての LoL ゲームについてのクエリには役に立ちません。可能な最善の方法は、すべての LoL ゲームについてクエリを実行した後に、3 月でフィルタすることです。この問題を解決するには次の 2 つの方法があります。

非正規化

  • それぞれのクエリに適切な行キーを持つ 2 つのテーブルを使用する。耐久性が高くスケーラブルなシステムになるため、これは良い解決方法です。

クエリとフィルタ

  • 上記スキーマのままで、1 つのクエリ(3 月のすべての LoL ゲーム)を実行します。非常に多くの行をフィルタリングするため、パフォーマンスが低下します。この方法では、スケーラビリティが低いシステムになり、使用量が増えると簡単にパフォーマンスが低下する可能性があるため、通常は良い解決方法ではありません。

行キーのホットスポット化を回避する

Cloud Bigtable での時系列でよくある問題はホットスポット化です。この問題は、単調に増加する値が含まれるすべてのタイプの行キーに影響を与える可能性があります。

簡単に言えば、時系列の行キーにタイムスタンプが含まれている場合に、1 つのノードがすべての書き込みのターゲットになり、そのノードに値が入力された後に、クラスタ内の次のノードに移動することになり、ホットスポット化が起こります。たとえば、携帯電話の電池のステータスをテーブルに保存していて、行キーが「BATTERY」とタイムスタンプ(下記を参照)で構成されている場合、その行キーは次々と増え続けます。Cloud Bigtable では、隣接する行キーは同じサーバーノードに保存されるため、すべての書き込みはノードがいっぱいになるまで 1 つのノードに集中し、いっぱいになった時点で書き込みはクラスタ内の次にノードに移動します。

行キー 列データ
BATTERY#20150301124501001 METRIC:USER:Corrie METRIC:PERCENTAGE:98
BATTERY#20150301124501002 METRIC:USER:Jo METRIC:PERCENTAGE:54
BATTERY#20150301124501003 METRIC:USER:Corrie METRIC:PERCENTAGE:96
BATTERY#20150301124501004 METRIC:USER:Sam METRIC:PERCENTAGE:43
BATTERY#20150301124501005 METRIC:USER:Sam METRIC:PERCENTAGE:38

この問題を解決するには、いくつかの方法があります。

  • フィールド プロモーション。 フィールドを列データから行キーに移動して、書き込みが連続しないようにします。

  • ソルティング。 計算済みの要素を行キーに追加して、人為的に書き込みが連続しないようにします。

Key Visualizer ツールを使用すると、ホットスポットの特定に役立つため、Cloud Bigtable に関する問題のトラブルシューティングが簡単になります。

フィールド プロモーション

この例では、USER を列から行キーの要素にプロモーションしています。この変更により、ユーザー識別子によって行キーがより均等に分散されるため、ホットスポット化の問題が解消されます。その結果、書き込みがクラスタ内の複数のノード間で分割されることになります。

フィールド プロモーションの利点は、クエリがより効率的になることも多いことです。そのため、この方法が最適です。この方法の(わずかな)欠点は、プロモートしたフィールドによってクエリが制限されるため、正しいフィールドをプロモートしなかった場合にやり直す必要があることです。

行キー 列データ
BATTERY#Corrie#20150301124501001 METRIC:PERCENTAGE:98
BATTERY#Corrie#20150301124501003 METRIC:PERCENTAGE:96
BATTERY#Jo#20150301124501002 METRIC:PERCENTAGE:54
BATTERY#Sam#20150301124501004 METRIC:PERCENTAGE:43
BATTERY#Sam#20150301124501005 METRIC:PERCENTAGE:38

ソルティング

この例では、タイムスタンプのハッシュ値を算出し、それを 3 で割ってその剰余を行キーに追加します。なぜ 3 なのでしょうか。この数字は、この例でのクラスタ内のノード数の推定値であり、アクティビティがそれらのノード間で適切に分担されることになります。

ソルティングの利点はそのシンプルさです。本質的には単純なハッシュ関数です。しかし、ソルティングの欠点として、時間範囲のクエリを実行する場合に、複数のスキャン(ソルト値ごとに 1 回のスキャン)を行ってその結果をユーザーのコード内で結合する必要があることが挙げられます。また、ノード間でアクティビティを分散させるためと、システムをスケールアップまたはスケールダウンしたときに良好に動作するための両方に適切なソルト値を選ぶのが難しいという欠点もあります。これらの欠点のため、また、人間が読むことのできる行キーを使用するのが最善であるため、ホットスポット化を防ぐ他の方法が見つからない場合を除きソルティングは避けてください。

行キー 列データ
BATTERY#1#20150301124501003 METRIC:USER:Jo METRIC:PERCENTAGE:96
BATTERY#1#20150301124501004 METRIC:USER:Sam METRIC:PERCENTAGE:43
BATTERY#2#20150301124501002 METRIC:USER: Corrie METRIC:PERCENTAGE:54
BATTERY#3#20150301124501005 METRIC:USER:Sam METRIC:PERCENTAGE:38
BATTERY#3#20150301124501001 METRIC:USER:Corrie METRIC:PERCENTAGE:98

デフォルトではフィールド プロモーションを優先します。 フィールド プロモーションではほとんどすべての場合にホットスポット化が回避され、クエリを容易にする行キーの設計がより簡単になる傾向があります。

フィールド プロモーションでホットスポット化が解消されない場合にのみソルティングを使用します。 ソルティング関数を適用するまれなケースでは、基本となるクラスタのサイズについて決めつけすぎないように注意します。上記の例では、クラスタに 3 つのノードがあることを仮定しているソルティング関数を使用していますが、Cloud Bigtable クラスタに存在できる限定されたノード数に拡張されるため、この仮定は問題ありません。数百のノードを持つクラスタを作成する可能性がある場合は、別のソルティング関数を使用します。

タイムスタンプの反転は必要な場合にのみ行う

タイムスタンプは、お使いのプログラミング言語での長整数の最大値(Java であれば java.lang.Long.MAX_VALUE など)からタイムスタンプの値を引くことによって反転できます。タイムスタンプを反転することによって、最新のイベントがテーブルの末尾ではなく先頭に現れる行キーを設計できます。そうすると、テーブルの先頭の N 個の行を取得するだけで、直近の N 個のイベントを取得できます。

最新の値に対するクエリを最もよく使用する場合にのみタイムスタンプの反転を優先します。 これは、タイムスタンプを反転すると、それ以外のすべてのクエリがより複雑になり、スキーマ全体が扱いにくくなるためです。

データ列設計のパターン

行は大きくできるが無制限ではない

Cloud Bigtable の行は 100 個までの列ファミリーと数百万の列を持つことができ、1 つの列に保存される各値は 100 MB までに制限されています。これらの十分な量の上限によって高い柔軟性が提供されています。ただし、大きい行を使用することがデータを保存する正しい方法であり、各行にできるだけ多くのデータを入れるべきである、とは考えないでください。多数の値を取得するには時間と費用が余計にかかることに注意してください。

通常は、行のサイズを 100 MB 未満に抑えます。これはルールというよりガイドラインです。行は 100 MB より大きくできます。ただし、これより大きい多数の行があると、パフォーマンス低下の問題が起こる可能性が高くなります。

通常は、列の値を 10 MB 未満に抑えます。これもルールというよりガイドラインです。10 MB より大きい値を保存できますが、パフォーマンス低下の問題が起こる可能性が高くなります。

繰り返しになりますが、大きい行や大きい個々の値に依存することが多い場合は、システムのパフォーマンス低下の問題が起こる可能性が高くなります。

Cloud Bigtable はリレーショナル ストアではなくキー / 値のストアです。結合や、1 つの行内以外でのトランザクションはサポートされていません。そのため、個々の行のデータまたは連続した一連の行のデータにアクセスするのが最適です。

このパターンの 1 つの結果は極めて明白です。大多数のケースで、時系列クエリでは一定期間の一定のデータセットがアクセスされています。したがって、ホットスポット化が起きる場合以外は、一定期間のすべてのデータが連続した行に保存されるようにします。

別の結果として、行または行範囲からデータを読み取った場合に、そのデータはそれ自体で有用であり、そのデータを他のデータ結合する必要はありません。たとえば、ショッピング ウェブサイトでのユーザー アクティビティを保存していて、そのユーザーが行った直近の 5 つのアクションをサイドバーに表示できるように、そのデータを取得する必要があるとします。この場合は、データを非正規化し、ユーザーと商品の一部の詳細を最近のアクションのテーブルに含めることを検討します。一方、リレーショナル データベースでは、1 つのテーブルにユーザー ID と商品 ID を保存し、SQL クエリでそのテーブルを個別のユーザーのテーブルと商品のテーブルと結合するでしょう。

つまり、エンティティに関するすべてのデータを各行に含める必要はありません。たとえば、ユーザーの直近のアクションに関する情報を表示する場合に、そのユーザーの電話番号や商品のメーカーの住所はサイドバーに表示しないため、それらの情報を保存しておく必要はありません。

クエリに適合するようにデータを非正規化しておきます。ただし、そのクエリで必要なデータだけを含めます。

1 回のクエリでアクセスするデータを 1 つの列ファミリーに保存する

1 つの列ファミリー内の列修飾子には、物理的関係と論理的関係があります。通常、1 つの列ファミリー内のすべての列修飾子は一緒に保存され、一緒にアクセスされ、一緒にキャッシュされます。そのため、1 つの列ファミリーにアクセスするクエリは、列ファミリー間にまたがるクエリより効率的に実行される可能性があります。

よく使用するクエリは、できるだけ少ない列ファミリーからデータを取得して、できるだけ効率的になるようにします。

1 つの行のアトミック性を利用しない

Cloud Bigtable ではトランザクションはサポートされていませんが、例外が 1 つあり、1 つの行での操作はトランザクションです。トランザクションは費用がかかる処理でもあり、トランザクションに依存しているシステムは、依存していないシステムよりパフォーマンスが低下します。

時系列を操作する際に、行のトランザクションの動作を活用しないでください。 既存の行でのデータの変更内容は、既存の行で変更するのではなく、別の新規の行として保存する必要があります。これはより簡単に構築できるモデルであり、列バージョンに頼らずにアクティビティの履歴を維持できます。

スキーマ設計の例

ここでは、スキーマ設計パターンを適用して次のデータタイプの例を作成します。

  • 金融市場データ
  • サーバーの指標
  • インテリジェント電力メーター(モノのインターネット)

これは例にすぎません。自分の時系列データに最適なスキーマを見つけるには、どのようなデータを保存するかと、どのようにデータのクエリを行うかを検討してから、前のセクションの設計パターンを適用する必要があります。

金融市場データ

この例では、架空の株式に関する情報を表す、架空の株式市場データのメッセージを扱います。

フィールド データの例
Ticker Symbol ZXZZT
Bid 600.55
Ask 600.60
Bid size 500
Ask size 1500
Last sale 600.58
Last size 300
Quote time 12:53:32.156
Trade time 12:53:32.045
Exchange NASDAQ
Volume 89000

始める前に、株式市場データのメッセージに関するいくつかの考察を示しておきます。

  • 論理的には独立している相場データと取引データをこのメッセージに集約しています。

  • 数千という比較的大きい数のティッカー シンボル(ティッカー)があります。

  • アクティブに取引が行われている株式は比較的少ないため、これらのティッカーのうち数百のティッカーが、受信するメッセージの 90% を占めています。

  • メッセージの頻度は高く(1 秒あたり数百~数万)、1 秒あたり平均数千のメッセージを受信しています。

  • 通常、相場データまたは取引データの個別のクエリであり、両方が同時に行われることはありません。

  • 相場データと取引データの両方について、通常のクエリでは次の項目を指定します。

    • 市場(NASDAQ など)
    • ティッカー シンボル(ZXZZT など)
    • 開始時刻と終了時刻

これで、このユースケース用のテーブルを設計できます。

関連があるデータは同じテーブルに保持し、関連がないデータは別のテーブルに保持する

  • 相場データは QUOTE というテーブルに保存します。
  • 取引データは TRADE というテーブルに保存します。
  • クエリには任意の時間範囲を含めることができるため、各行には 1 つのメッセージからのデータを保存します。

行は大きくできるが無制限ではない

  • 各行には 1 つのメッセージからのデータを保存します。そのため、サイズの懸念はありません。

1 つの行のアトミック性を利用しない

  • 各メッセージは自己完結しているため、懸念はありません。

短くて意味のある名前にする

  • シンプルにするために、この例ではメッセージのフィールド名を大文字にして空白文字を削除した名前を使用します。

上記のように決めたことにより、列レイアウトは次のようになります。

QUOTE テーブルの例:

列データ
MD:SYMBOL:ZXZZT MD:BID:
600.55
MD:ASK:
600.60
MD:BIDSIZE:
500
MD:ASKSIZE:
1500
MD:QUOTETIME:
1426535612156
MD:EXCHANGE:
NASDAQ

TRADE テーブルの例:

列データ
MD:SYMBOL:
ZXZZT
MD:LASTSALE:
600.58
MD:LASTSIZE:
300
MD:TRADETIME:
1426535612045
MD:EXCHANGE:
NASDAQ
MD:VOLUME:
89000

次に、行キーを設計します。

縦長のテーブルを使用する

  • 各行には 1 つのメッセージからのデータを保存するため、比較的幅が狭くて数が多い行になります。

列バージョンより行を優先する

  • 列バージョンは、値が間違っていた場合の例外的な状況でのみ使用します。

クエリを考慮して行キーを設計する

  • QUOTETRADE の行キーは同じ形式にできます。
  • これは時系列であるため、デフォルトで QUOTETIME が行キーの一部になると仮定できます。
  • 開始時刻と終了時刻を指定して取引所とティッカーごとにクエリを行うには、EXCHANGESYMBOLQUOTETIME の値を使用する必要があります。
  • したがって、EXCHANGE(6 文字のコード。6 文字未満の取引所には右側に空白文字が詰められる)、SYMBOL(5 文字のコード。5 文字未満のティッカーには右側に空白文字が詰められる)、QUOTETIME(13 桁の数値)をプロモートします。EXCHANGESYMBOL に空白を詰めることによって、行キーの各部分のオフセットが予測可能になっています。
  • これらの値を 1 つにまとめることによって、行キーは EXCHANGE + SYMBOL + QUOTETIME の形式(例: NASDAQ#ZXZZT#1426535612156)になっています。

行キーのホットスポット化を回避する

  • EXCHANGESYMBOL を行キーの先頭に配置することによって、アクティビティを自然に分散できます。
  • メッセージの 90% が数百のティッカーに集中した場合にはホットスポット化のリスクがありますが、さらに変更する前にシステムのストレステストを実施する必要があります。そのような集中によってパフォーマンスが低下した場合は、ソルティングを適用してアクティビティをより効率的に分散します。

タイムスタンプの反転は必要な場合にのみ行う

  • このケースでは、クエリで直近のデータが必要になるとは限らないため、タイムスタンプの反転は使用しません。

この設計演習を終えると、次のようなテーブルが得られます。

QUOTE テーブルの例:

行キー 列データ
NASDAQ#ZXZZT#1426535612156 MD:SYMBOL:
ZXZZT
MD:BID:
600.55
MD:ASK:
600.60
MD:BIDSIZE:
500
MD:ASKSIZE:1
500
MD:QUOTETIME:
1426535612156
MD:EXCHANGE:
NASDAQ

TRADE テーブルの例:

行キー 列データ
NASDAQ#ZXZZT#1426535612045 MD:SYMBOL:
ZXZZT
MD:LASTSALE:
600.58
MD:LASTSIZE:
300
MD:TRADETIME:
1426535612045
MD:EXCHANGE:
NASDAQ
MD:VOLUME:
89000

これらのテーブルは 1 日あたり数億行の割合で増加しますが、Cloud Bigtable では問題なく扱うことができます。

サーバーの指標

以下の例では、マシンのさまざまな指標(コアごとの CPU 使用率、メモリ使用率、ディスク使用率、など)を収集する、架空のサーバー監視システムを使用しています。この例のスキーマは複数回繰り返されています。

データについては次のことを仮定できます。

  • マシンごとに 100 個の指標を収集します。
  • 100,000 台のマシンから指標を収集します。
  • 指標は 5 秒ごとに収集されます。
  • 通常のクエリは次のいずれかです。

    • 指定された開始時刻と終了時刻の間の、指定したマシンの指標。
    • マシンのインベントリ全体での最新の指標。

ユースケースを考慮すると、テーブルを設計できます。

繰り返し 1

関連があるデータは同じテーブルに保持し、関連がないデータは別のテーブルに保持する

  • 指標データを METRIC というテーブルに保存します。
  • 指標にはいくつかのカテゴリがあるため、適切な列ファミリーを使用して指標をグループ化します。
  • クエリには任意の時間範囲を含めることができるため、所定の期間に 1 台のマシンから収集した 1 セットの指標を各行に保存します。

行は大きくできるが無制限ではない

  • 各行に 1 セットの指標を保存するため、サイズの懸念はありません。

1 つの行のアトミック性を利用しない

  • このスキーマ設計では行のアトミック性に依存しません。

短くて意味のある名前にする

  • シンプルにするために、指標のフィールド名を大文字にして空白文字を削除した名前を、列修飾子として使用します。

上記のように決めたことにより、列レイアウトは次のようになります。

列データ
METRIC:
HOSTNAME:
server1.bbb.com
METRIC:
CPU/CPU1_USR:
0.02
METRIC:
CPU/CPU1_NICE:
0.00
... METRIC:
IO/BLK_READ:
253453634
METRIC:
MIO/BLK_WRTN:
657365234

次に、パターンに基づいて行キーを設計します。

縦長のテーブルを使用する

  • テーブルの各行に 1 台のマシンの 1 セットの指標が保存されるため、行数が非常に多くなります。

列バージョンより行を優先する

  • 列バージョンは使用しません。

クエリを考慮して行キーを設計する

  • これは時系列であるため、タイムスタンプ TS を行キーに含めます。
  • 開始時刻と終了時刻を指定して、指定したマシンの指標を取得するには、HOSTNAMETS を使用して行の範囲を取得します。
  • マシンのインベントリ全体での最新の指標を取得する手順は複雑です。インベントリ内の各マシンが取得される保証はないため、タイムスタンプを反転して N 個の行をスキャンすることはできません。

ここで、行キーの設計での問題にぶつかりました。ここでの解決方法は非正規化することです。最新バージョンの指標を保持する CURRENT_METRIC という別のテーブルを作成し、METRIC を更新したときにはこのテーブルも必ず更新します。マシンの既存の指標を更新する際に、そのマシンの行を上書きするだけです。

次に、元の設計で以下の手順を繰り返します。

繰り返し 2

関連があるデータは同じテーブルに保持し、関連がないデータは別のテーブルに保持する

  • 指標データを METRIC というテーブルに保存します。
  • 最新バージョンの指標を CURRENT_METRIC というテーブルに保存します。
  • その他の情報は繰り返し 1 と同じままにします。

行は大きくできるが無制限ではない

  • 繰り返し 1 と同じままにします。

1 つの行のアトミック性を利用しない

  • CURRENT_METRIC 内の各マシンのデータを更新するために、行のアトミック性に依存します。これは、競合の可能性がほとんどないシンプルな行 mutation であるため、問題は起こりません。

短くて意味のある名前にする

  • 繰り返し 1 と同じままにします。

次に、パターンに基づいて行キーを設計します。

縦長のテーブルを使用する

  • 両方のテーブルで、1 台のマシンの 1 セットの指標がテーブルの各行に保存されるため、行数が多くなります。

列バージョンより行を優先する

  • 繰り返し 1 と同じままにします。

クエリを考慮して行キーを設計する

  • これは時系列であるため、タイムスタンプ TS を行キーに含めます。開始時刻と終了時刻を指定して、指定したマシンの指標を取得するには、HOSTNAMETS を使用して METRIC から行の範囲を取得します。
  • したがって、HOSTNAME を行キーにプロモートして、HOSTNAME + TS の形式の行キーを使用します。
  • 特定のマシンの最新の指標を見つけるには、マシンの行キーの接頭辞か HOSTNAME を指定してフィルタし、CURRENT_METRIC をスキャンします。
  • マシンのインベントリ全体の最新の指標を見つけるには、行キーを指定せずに CURRENT_METRIC をスキャンします。
  • したがって、その他のフィールドを行キーにプロモートする必要はないため、行キーは HOSTNAME + TS の単純な形式になります。これによって、テーブルを効率的にシャード化できる、シンプルで簡単に理解できるスキーマになります。
  • CURRENT_METRIC テーブルに各テーブルの最新の指標が常に保存されていることがわかっていても、単純化のために、行キーは再度 HOSTNAME + TS にします。

    METRICCURRENT_METRIC の行キーの例: server1.aaa.bbb.com#1426535612045

行キーのホットスポット化を回避する

  • METRICCURRENT_METRIC の両方で、行キーの先頭にホスト名があることによってアクティビティがリージョン間で分散されるため、ホットスポット化の懸念はありません。

タイムスタンプの反転は必要な場合にのみ行う

  • 最新の指標のデータを個別のテーブルに保存しているため、タイムスタンプを反転する必要はありません。

この設計演習を終えると、次のようなテーブルが得られます。

METRIC と CURRENT_METRIC のテーブルの例:

行キー 列データ
server1.bbb.com#1426535612045 METRIC:
CPU/CPU1_USR:
0.02
METRIC:
CPU/CPU1_NICE:
0.00
... METRIC:
IO/BLK_READ:
253453634
METRIC:
MIO/BLK_WRTN:
657365234

これらのテーブルは 1 日あたり約 20 億行の割合で増加しますが、Cloud Bigtable では問題なく扱うことができます。

インテリジェント電力メーター(モノのインターネット)

この例では、インテリジェント電力メーターがセンサーの計測値を一元化されたシステムに定期的に送信する、架空の IoT シナリオを使用しています。この例でも、スキーマは複数回繰り返されています。

データについては次のことを仮定できます。

  • 10,000,000 台の電力メーターが稼働している。
  • 各メーターはセンサーの計測値を 15 分ごとに送信する。
  • メーター ID はユニークな数値の ID である。
  • 通常のクエリは次のいずれかです。

    • 指定した日の指定したメーターのすべてのデータ
    • 指定した日のすべてのデータ

ユースケースを考慮すると、テーブルを設計できます。

関連があるデータは同じテーブルに保持し、関連がないデータは別のテーブルに保持する

  • センサーデータを SENSOR というテーブルに保存します。
  • クエリは 1 日単位で増加するため、1 台のメーターの 1 日分のデータを各行に保存します。

行は大きくできるが無制限ではない

  • 各行には 1 台のメーターの 1 日分のデータが保存されるため、合計で 96 列であり(1 日は 24 時間 * 1 時間は 60 分 / 15 分おきに 1 回の読み取り)、サイズの懸念はありません。

1 つの行のアトミック性を利用しない

  • データの受信時に、該当する日の行にそのデータが追加されるため、行のアトミック性を利用します。これは、競合の可能性がほとんどないシンプルな行 mutation であるため、問題は起こりません。

短くて意味のある名前にする

  • ID にメーター ID(ユニークな整数)を保存します。
  • 00002345 という名前の一連の列に、その日に 15 分ごとに記録された 96 個の値が入ります。必要に応じて頻度を 15 分以外に変更できるために、このスキーマが採用されています。

上記のように決めたことにより、列レイアウトは次のようになります。

繰り返し 1

列データ
METER:ID:987654 METER:0000:
12.34
METER:0015:
13.45
... METER:2330:
27.89
METER:2345:
28.90

次に、行キーを設計します。

縦長のテーブルを使用する

  • 前述のように、各行には 1 日分のデータが保存されます。

列バージョンより行を優先する

  • 列バージョンは、値が間違っていた場合の例外的な状況でのみ使用します。

クエリを考慮して行キーを設計する

  • これは時系列であるため、DATE を行キーに含めます。関心があるのは日付の精度だけのため、タイムスタンプの下 5 桁はゼロにして省略されます。
  • 指定した日の、指定したメーターのデータのクエリを行うには、METERDATE を使用して 1 つの行を取得します。
  • 指定した日のすべてのメーターのデータのクエリを行うには、DATE を使用して行の範囲を取得します。
  • したがって、DATE(8 桁の数値)と METER(10 桁の数値。辞書順を保持しながら 10 億台のメーターを許容できるように、メーター ID を左側に詰めて 10 桁にする)をプロモートします。
  • クエリを 1 つにまとめることによって、行キーは DATE + METER の形式にする必要があります(例: |20170726|0000987654|)。

この時点で、1 つの問題に気付くでしょう。日付が行キーの先頭に配置されているため、各日の開始時にすべてのメーターが 1 つのノードにヒットします。1000 万台のメーターがあれば、これはすべての日にパフォーマンスに影響を与える問題になる可能性が高くなります。解決方法は、特定の日のデータをメーターにクエリするためのより良い方法を見つけることです。毎夜 1 回のクエリを実行してその結果を SENSOR_YYYYMMDD という名前の新しいテーブルに保存すれば、日付ベースのクエリ用に行キーを最適化する必要はなくなります。

繰り返して、この問題を解消しましょう。

繰り返し 2

関連があるデータは同じテーブルに保持し、関連がないデータは別のテーブルに保持する

  • センサーデータを SENSOR というテーブルに保存します。
  • クエリは 1 日単位で増加するため、1 台のメーターの 1 日分のデータを各行に保存します。
  • SENSOR_YYYYMMDD(日付)という別のテーブルを生成して、その日のすべてのメーターのデータを保存する、バッチによるクエリを夜間に実行します。

行は大きくできるが無制限ではない

  • 繰り返し 1 と同じままにします。

1 つの行のアトミック性を利用しない

  • 繰り返し 1 と同じままにします。

短くて意味のある名前にする

  • 繰り返し 1 と同じままにします。

これをすべて 1 つにまとめると、SENSOR テーブルと SENSOR_YYYYMMDD テーブルの両方で、例の行は次のようになります。

列データ
METER:ID:987654 METER:0000:
12.34
METER:0015:
13.45
... METER:2330:
27.89
METER:2345:
28.90

次に、行キーを設計します。

縦長のテーブルを使用する

  • 繰り返し 1 と同じままにします。

列バージョンより行を優先する

  • 繰り返し 1 と同じままにします。

クエリを考慮して行キーを設計する

  • これは時系列であるため、DATE を行キーに含めます。
  • 指定した日の、指定したメーターのデータのクエリを行うには、METERDATE を使用して 1 つの行を取得します。
  • したがって、DATE(8 桁の数値)と METER(10 桁の数値。辞書順を保持しながら 10 億台のメーターを許容できるように、メーター ID を左側に詰めて 10 桁にする)をプロモートします。
  • 行キーは METER + DATE の形式(例: "0000987654#20170726")である必要があり、これによってクエリの要求が満たされ、ノード間でアクティビティが適切に分散されるようになります。
  • テーブル全体をスキャンして、前日のデータを SENSOR_YYYYMMDDYYYYMMDD は前日の日付)という新しいテーブルに保存する、バッチによるクエリも毎日 1 回実行します。

行キーのホットスポット化を回避する

  • SENSOR テーブルにはホットスポット化の懸念はありません。行キーの先頭に METER があるため、書き込みは均等に分散されます。
  • SENSOR_YYYYMMDD テーブルにはホットスポット化の懸念はありません。各テーブルは、パフォーマンスがあまり問題にならないバッチによるクエリとして、一度だけ構築されます。ただし、これらのテーブルの作成時には SENSOR の完全スキャンが必要であるため、SENSOR テーブルへのその他のクエリがほとんどないときに SENSOR_YYYYMMDD テーブルを作成します。

タイムスタンプの反転は必要な場合にのみ行う

  • このケースでは、タイムスタンプを反転する必要はありません。

この設計演習を終えると、SENSOR テーブルと SENSOR_YYYYMMDD テーブルの両方は次のようになります。

行キー 列データ
0000987654#20170726 METER:ID:987654 METER:0000:
12.34
METER:0015:
13.45
... METER:2330:
27.89
METER:2345:
8.90

このテーブルは 1 日あたり 1,000 万行より少ない割合で増加しますが、Cloud Bigtable では問題なく扱うことができます。

次のステップ

このページは役立ちましたか?評価をお願いいたします。

フィードバックを送信...

Cloud Bigtable ドキュメント