Cloud Bigtable スキーマのヒント: キーのソルティング
Google Cloud Japan Team
※この投稿は米国時間 2022 年 10 月 19 日に、Google Cloud blog に投稿されたものの抄訳です。
Cloud Bigtable は、低レイテンシ、高スループットの NoSQL データベースです。NoSQL を使用して、スケーラブルでビジネスの成長に合ったスキーマを設計できます。実際に大規模なデータセットを扱うときには、よりアクティビティが顕著なアクセス パターンの外れ値が生じる可能性があり、さらに綿密な計画が必要になります。この記事では、非常にアクティブな行がある以外はバランスのとれたスキーマの、アクティブな行のパフォーマンスを向上させるために、Bigtable のスキーマを最適化する方法を説明します。
行キー設計の復習
Bigtable は、スループットが行キーの範囲全体にわたって均等に分散され、すべてのノードに分散可能なときに最大の力を発揮します。Bigtable の行は、連続した行キーのグループを含むタブレットに物理的に保存されており、各タブレットは使用可能なノードに分散されています。同一のタブレットの行が、ノード内の他のタブレットと比べて不均衡なほど過剰なリクエストを受け取っていた場合、それがパフォーマンスに影響する可能性があります。
通常、行キーは特定のクエリ向けに最適化されるように設計されています。たとえば、個々のユーザーに焦点を当てたクエリを作成する場合、次のように先頭にユーザー ID を付けます: user_id-xxx-yyy。有名人のアカウントなどのように、一部のユーザーが他のユーザーに比べて著しくアクティブである場合は、特定のノードに負荷がかかりすぎることで、それらの行からの書き込みと読み取りがホットスポット化する場合があります。
複数のタブレット間で物理的に分割することで、論理行を分散することができれば、使用可能なノードにわたって行が均一化され、ホットスポットが軽減されます。
キー接頭辞のソルティング
適切に分散されているユーザー ID は、通常、行キー接頭辞として機能するため、これを行キーの設計の出発点として使用することができます。
user_id-xxx-yyy
すべての Bigtable ノードにわたってこの不均衡なスループットを分散するための一つの戦略としては、行キーの設計の先頭に追加の値を付加する方法があります。
01-user_id-xxx-yyy
02-user_id-xxx-yyy
この例では物理行が 2 行あり、スループットを半分に分割する 1 つの論理行に対応しています。これにより、残りのキースペースにわたって特定のユーザー ID のすべての行が分散されます。接頭辞が異なるため、別々のタブレットに存在することができ、高い確率で複数のノードでホストされています。しかし、このセットアップの目標はロード バランシングのメカニズムにさらなる選択肢をもたらすことですので、両方の接頭辞が同じノード内にあるか、1 つの接頭辞が複数のノードに分かれている可能性もありますのでご注意ください。
接頭辞の選択
接頭辞を選択するうえでは、リクエストをさらに複雑にしないことが重要な検討事項です。ランダムな接頭辞を使用した場合、各 GET リクエストが複数の GET リクエストとなり、確実に正しい行が見つけられるようになります。接頭辞が行キーから確定的であった場合は、単一行の読み取り / 書き込みリクエストに対する最低限の変更のみ可能となります。
N 除算を行う場合は、既存の行キー全体のハッシュの剰余 N を使用することができます。また、N をソルト範囲とみなすこともできます。
物理キーとして機能する検索および書き込みのポイントは、論理キーから算出することができます。ソルティングではホットスポットを除外することはできませんが、強さ 1/N の N ホットスポットに拡大していきます。こうした、それほど深刻ではないホットスポットは、個別ノードで簡単に処理できるようになります。
接頭辞のオプション
そのままにしておきたい接頭辞に対して一般的なスキャンを行う場合、行キー全体ではなく、一部の行キーだけに対してハッシュすることも可能です。
user_id-site-timestamp
の形式の行キーであれば、user_id
と site
の組み合わせを効率的にスキャンすることをおすすめします。ここでは、ハッシュを作成するときにタイムスタンプを残せるため、それらの組み合わせの時系列データを常にまとめてグループ化することができます。
頻繁にスキャンされる同じ論理的な接頭辞を伴うキーも、効率的にスキャンすることが可能です。
この戦略はホットスポットへの耐性が低く、個別の user_id
、site
の組み合わせに著しいアクセスがあれば、ソルティング戦略で軽減したはずの同様の問題が再び生じることになります。
実装
実際にコードで実行するためには、Bigtable のデータにリクエストを作成しているエリアを変更する必要があります。完全なソースコードの例は、GitHub からご確認いただけます。
書き込み
この新しい手法を使用してデータを書き込む場合は、以下の手順を実施してください。
書き込みをしたい行キーを準備します
ハッシュ関数を使って接頭辞を計算します
接頭辞と行キーを連結して、ソルト付き行キーを構築します
その後でデータの書き込みにソルト付き行キーを使用します
どこにデータを書き込む場合でも、この手順を確実に組み込む必要があります。
Java では次のようになります。
読み取り
取得
ソルト付きキーでテーブルの個別の行を読み取るためには、次のようなデータの書き込みと同じ最初の手順を実施する必要があります。
読み取りたい行キーを準備します
ハッシュ関数を使って接頭辞を計算します
接頭辞と行キーを連結して、ソルト付き行キーを構築します
その後でデータの読み取りにソルト付き行キーを使用します
物理的な行キーは論理的な行キーから確定的に計算されているため、実行する必要のある読み取りは論理キーごとに 1 回のみです。
Java では次のようになります。
スキャン
スキャンごとに次の手順を実施してください。
スキャンしたい行キーの接頭辞を準備します
0 から N(可能性のある各ソルト オプション)の範囲において
接頭辞と行キーを連結して、ソルト付き行キーを構築します
その後で接頭辞のスキャンにソルト付き行キーを使用します
並行してスキャンを実行します
すべてのスキャンの結果を組み合わせます
例を見てみましょう。たとえば、1 人のユーザーの 1 つのサブカテゴリのすべてのデータを取得する場合、「user_id-xxx-」という接頭辞のスキャンを実行します。ソルト付き行を使用しているのであれば、ハッシュサイズの大きさに応じて接頭辞のスキャンを行う必要があります。ハッシュサイズが 4 である場合は、接頭辞のスキャンを 4 回行います。
01-user_id-xxx-
02-user_id-xxx-
03-user_id-xxx-
04-user_id-xxx-
最高のパフォーマンスを実現するためには、すべての接頭辞を 1 つのリクエストで送信するのではなく、各スキャンを並行して実行するようにします。その際、リクエストが並行して行われるため、行が戻ってきたときに並べ替えた順番になっていない場合があります。行の順番が重要である場合は、結果を受け取った後に追加で並べ替える必要があります。
また、物理的な行キーが連続する範囲ではなくなるため、これらのスキャンでより多くの Bigtable CPU が消費される可能性があります。大量のスキャンを行うワークロードの場合は、ソルティングの要素を選択することが重要な検討事項となります。しかしながら、大規模スキャンでは、リクエストに対応する際に同時に多くのリソースが使用されるため、効率が良くなる場合があります。
Java では次のようになります。
先を見越した移行
既存のデータセットに大幅な変更を行うのは難しい場合もありますが、そのような場合はソルトの適用のみを進めるという移行も一つの方法です。キーの末尾にタイムスタンプがあって、特定時点を経過したソルト行キーにコードを変更すると、古い既存のキーにソルトなしのキーのみが使用されます。
次のステップ
Bigtable のパフォーマンスの詳細を確認する
Bigtable にキャッシュ レイヤを追加するといった代替のソリューションが活用できないかを確認する
- Cloud Bigtable 担当デベロッパー アドボケイト Billy Jacobson