Rowkey は、HBase テーブル内の各行の一意の識別子です。データの格納方法、パーティション分割方法、およびアクセス方法を制御します。大規模にデータを書き込む前に、Rowkey を慎重に設計してください。
このトピックでは、ログデータとトランザクションデータのトレードオフと例を含む、5つの設計上の考慮事項について説明します。
Rowkey の仕組み
クエリメソッド
HBase は2つのクエリメソッドをサポートしており、それぞれRowkey の設計に異なる制約があります。
| メソッド | 説明 | 制約 |
|---|---|---|
| GET | 完全な Rowkey で単一の行をルックアップします。 | Rowkey を構成するすべてのフィールドが既知である必要があります。 |
| Scan | 開始キーと終了キーの間の行の範囲を読み取ります。 | プレフィックスベースの範囲のみがサポートされます。 |
スキャンにおけるプレフィックス制約: スキャンは、特定のプレフィックスで始まる行と一致しますが、サフィックスでクエリしたり、Rowkey の中央の値と一致させたりすることはできません。たとえば、Rowkey が辞書語である場合、スキャンは pre で始まるすべての単語を見つけることができますが、ing で終わる単語を見つけることはできません。
プレフィックススキャンとして表現できないクエリの場合は、次のいずれかのアプローチを使用してください。
逆キー構造を持つインデックステーブルを作成します。
不要な行を破棄するためにサーバー側フィルターを適用します。
セカンダリインデックスを使用します。
Rowkey の一意性とバージョン
同じ Rowkey を持つ行は、複数のバージョンを持つ単一のレコードとして扱われます。デフォルトでは、GET は最新バージョンを返します。意図的に Multi-Version Concurrency Control (MVCC) を使用しない限り、Rowkey は一意である必要があります。
Rowkey はデータベースのプライマリキーのように使用してください。単一のフィールドまたは複数のフィールドの複合にすることができます。
[user_id]— ユーザーごとに1つのレコード[user_id][order_id]— ユーザーごとに複数のレコード
設計上の考慮事項
データ分散: ホットスポットの回避
HBase は、Rowkey の範囲 (辞書式順序) に従ってリージョンサーバー全体にデータを分散します。多くの書き込みが共通プレフィックス (たとえば、2024-01-01T00:00:01 のようなタイムスタンプ優先キー) を共有する場合、すべての書き込みが同じリージョンサーバーに着地します。これにより、書き込みスループットを低下させ、他のサーバーをアイドル状態にするホットスポットが作成されます。
書き込みをリージョンサーバー全体に分散するには、次のいずれかの手法を使用してください。
ハッシュプレフィックスによるソルト化
MD5 ハッシュの最初の数文字を Rowkey の先頭に追加します。ハッシュは決定論的であるため、同じ入力は常に同じプレフィックスにマッピングされ、読み取りは効率的です。
[md5(user_id).subStr(0, 4)][user_id][order_id]トレードオフ: 同じユーザーの行は異なるリージョンに分散されます。単一ユーザーの範囲をスキャンするには、複数のターゲットGET またはフィルター付きスキャンが必要です。
キーの反転
カーディナリティの高いプレフィックスフィールドを反転します。たとえば、時間とともに増分するユーザー ID を反転すると、先頭バイトがランダム化されます。
[reverse(user_id)][order_id]トレードオフ: 自然な順序が失われるため、反転されたフィールドでの範囲スキャンは意味がありません。
剰余演算によるバケット化
剰余演算を使用して各行をバケットに割り当て、バケット番号を先頭に追加します。これは、タイムスタンプが単調増加する時系列データに効果的です。
long bucket = timestamp % numBuckets;
[bucket][timestamp][hostname][log_event]トレードオフ: 特定の時間範囲のすべてのデータを取得するには、すべての numBuckets 範囲をスキャンし、結果をマージする必要があります。
ランダムサフィックスの追加
複数の行に書き込みを分散するために、乱数を追加します。
[user_id][order_id][random(100)]トレードオフ: 特定のレコードを読み取るには、ランダムサフィックスを知る必要があります。インデックスなしではポイントルックアップは非実用的です。
選択方法: 分散された行全体をスキャンする必要がある場合 (個々のレコードをルックアップするだけでなく)、ランダムサフィックスではなくハッシュ化を使用してください。ハッシュ化は決定論的であるため、読み取りを効率的にルーティングできます。
Rowkey の長さ: 短く保つ
Rowkey は、HBase のすべての列値とともに格納されます。長い Rowkey は、すべての行のすべての列でストレージオーバーヘッドを増大させます。Rowkey は可能な限り短く保ってください。
文字列を数値型に置き換えます。
longは8バイトを占有し、文字列"2015122410"は10バイト、MD5 文字列は32バイトを占有します。"2015122410"の代わりにLong(2015122410)を使用してください。完全な名前の代わりにコードを使用します。 たとえば、
"Taobao"の代わりにtbを使用してください。
フィールド境界の明確さ: 部分一致の防止
Rowkey がデリミタなしで複数のフィールドを結合する場合、スキャン範囲は余分な行を返す可能性があります。たとえば、Rowkey が [column1][column2][column3] で、host1 から host2 までスキャンすると、行 host12... もその範囲に含まれます。
これを防ぐには、2つのアプローチがあります。
固定長パディング: 各フィールドを固定幅にパディングして、境界を曖昧さのないものにします。
[rpad(column1, 'x', 20)][column2]デリミタ: デリミタ文字でフィールドを区切ります。
[column1][_][column2]固定長パディングはスキャンに効率的です。デリミタは読みやすいです。
降順: タイムスタンプの反転を使用
デフォルトでは、HBase スキャンは昇順キー順で行を返します。最新のエントリを最初に取得する必要がある場合は、2つのオプションがあります。
オプション1: リバーススキャン API (scan.setReverse(true))
実装は簡単ですが、リバーススキャンはフォワードスキャンよりもパフォーマンスが低いです。降順が時折の要件である場合に使用してください。
オプション2: 行キー内のタイムスタンプの反転
生のタイムスタンプの代わりに Long.MAX_VALUE - timestamp を格納します。これにより、自然なソート順が反転され、新しいエントリがフォワードスキャンで最初に表示されます。
timestamp = Long.MAX_VALUE - timestamp;
[hostname][log_event][timestamp]降順が主要なアクセスパターンであり、スキャンパフォーマンスが重大である場合に使用してください。
設計例
適切な Rowkey の設計は、主要なアクセスパターンによって異なります。同じデータセットでも、クエリ方法によって異なる設計が必要になる場合があります。以下の例は、アクセスパターンが設計決定をどのように推進するかを示しています。
ログデータと時系列データ
データ要素は、hostname、log_event、timestamp です。
| アクセスパターン | Rowkey の設計 | 備考 |
|---|---|---|
| 時間範囲でホストのメトリックをクエリ | [hostname][log_event][timestamp] | ホストごとの範囲スキャンに効率的です。単一のホストが書き込みを支配する場合、ホットスポットを作成する可能性があります。 |
| ホストの最新レコードをクエリ | [hostname][log_event][Long.MAX_VALUE - timestamp] | タイムスタンプの反転により、フォワードスキャンで最新のエントリが最初に配置されます。 |
| 時間全体に書き込みを均等に分散 (大量のデータまたは支配的なホストなし) | [バケット][タイムスタンプ][ホスト名][ログイベント] ただし、バケット = タイムスタンプ mod numBuckets | 時間範囲の結果を集計するには、すべてのバケット範囲をスキャンする必要があります。 |
選択方法: ホストごとの範囲クエリが主要なユースケースである場合は、[hostname][log_event][timestamp] から開始してください。書き込みホットスポットが出現したり、時間範囲クエリが多くのホストにまたがる場合は、バケットパターンに切り替えてください。
トランザクションデータ
トランザクションには、買い手、売り手、注文番号の3つのロールが関与します。異なるアクセスパターンには異なる Rowkey の設計が必要であり、多くの場合、複数のテーブルが必要です。
| アクセスパターン | テーブル | Rowkey の設計 |
|---|---|---|
| 時間範囲で売り手の注文をクエリ | Seller table | [seller_id][timestamp][order_number] |
| 時間範囲で買い手の注文をクエリ | Buyer table | [buyer_id][timestamp][order_number] |
| 注文番号で注文をルックアップ | Index table | [order_number] |
3つのアクセスパターンすべてをカバーするように3つのテーブルすべてを設計してください。インデックステーブルを使用して order_number をルックアップし、その値で買い手または売り手テーブルをクエリしてください。
次のステップ
HBase データモデルの概要
HBase のセカンダリインデックス
HBase テーブルのパフォーマンスチューニング