このページでは、Cloud Bigtable 表のスキーマを設計する方法について説明します。このページを読む前に、Cloud Bigtable の概要を理解する必要があります。
一般概念
Cloud Bigtable スキーマの設計は、リレーショナル データベースのスキーマ設計とは大きく異なります。Cloud Bigtable スキーマを設計する際には、以下の点に注意してください。
- 各表のインデックス(行キー)は 1 つのみです。 二次インデックスはありません。
- 行は、行キーの(最小バイト文字列から最大バイト文字列まで)辞書的順番で並べ替えられます。行キーはビッグエンディアン(ネットワーク)バイトオーダーで並べ替えられます。これはバイナリのアルファベット順に相当します。
- すべての操作は行レベルでアトミックに実行されます。 たとえば、表の 2 つの行を更新する場合、一方の行の更新に成功し、他方の行の更新に失敗するという可能性があります。複数行にまたがるアトミック性を必要とするスキーマ設計は避けてください。
- 読み取りと書き込みは(表の行スペース全体に)均等に分散されるのが理想的です。
- 一般に、特定のエンティティの情報はすべて 1 行にまとめるようにします。 アトミックな更新および読み取りを必要としないエンティティは、複数の行に分割してもかまいません。エンティティ データが大きい(数百メガバイト)場合は、複数行に分割することをお勧めします。
- 関連するエンティティは隣接する行に格納するようにします。これにより、読み取りの効率が向上します。
- Cloud Bigtable 表はスパースです。 空の列は領域を消費しません。このため、大半の行で大部分の列が空であっても、多数の列を作成することは多くの場合、合理的です。
サイズの制限
スキーマ設計で、以下の推奨サイズ制限を超えないようにすることをおすすめします。
- 行キー: キーあたり 4 KB
- 列ファミリー: 表あたり 100 ファミリーまで
- 列修飾子: 修飾子あたり 16 KB
- 個々の値: セルあたり 10 MB
- 1 行内のすべての値: 100 MB まで
また、スキーマ設計により、以下のハード制限を超えないようにする必要があります。
- 個々の値: セルあたり 100 MB
- 1 行内のすべての値: 256 MB
- 表: クラスタあたり 1,000 個
行の読み取りはアトミックに行われるため、単一行に格納するデータ量を制限することは特に重要です。たとえば、100 MB のデータが格納されている行を 25 行読み込む場合に、インスタンスの空きメモリ容量が 2 GB しか残っていないと、メモリ不足エラーが発生します。
行キーの選択
Cloud Bigtable で最高のパフォーマンスを得るには、行キーの作成方法について慎重に検討することが不可欠です。というのは、ほとんどの効率的な Cloud Bigtable クエリでは、行キー、行プレフィックス、または行範囲を使用してデータを取得するからです。その他のタイプのクエリでは全表スキャンが行われるため、効率が大幅に低下します。設計の段階で正しい行キーを選択すれば、後で面倒なデータ移行処理を行わずに済みます。
まず、格納するデータの用途について確認します。次に例を示します。
- ユーザー情報: ユーザー間の接続に関する情報(たとえば、ユーザー A がユーザー B をフォローしているなど)に迅速にアクセスする必要がありますか?
- ユーザー生成コンテンツ: 大容量のユーザー生成コンテンツ(ステータスの更新など)のサンプルをユーザーに表示する場合、特定のユーザーに表示するステータス更新をどのように決定しますか?
- 時系列データ: 最新の N 個のレコードを検索する頻度と、特定の期間内のレコードを検索する頻度では、どちらが高くなりますか?さまざまなイベントのデータを格納する場合、イベントのタイプに応じてフィルタリングする必要がありますか?
事前にニーズを把握しておけば、行キーおよび全体的なスキーマ設計によって、データを効率的にクエリするための十分な柔軟性が得られます。
行キーのタイプ
このセクションでは、最もよく使用されるタイプの行キーと、ケースに応じた各タイプのキーの使い分けについて説明します。
だいたいの方向性として、行キーは適度に短くするようにします。長い行キーを使用すると、メモリとストレージが余分に消費されるだけでなく、Cloud Bigtable サーバーからのレスポンス返却に要する時間も長くなります。
リバース ドメイン名
ドメイン名として表現可能なエンティティに関するデータを格納する場合は、行キーとしてリバース ドメイン名(com.company.product など)を使用することを検討してください。リバース ドメイン名は、各行のデータが隣接する行と重なる傾向がある場合は特に有効です。そのような場合、Cloud Bigtable によるデータの圧縮効率が向上します。
このアプローチは、データが多数の異なるリバース ドメイン名間に分散している場合に、最も効果的です。大部分のデータが少数のリバース ドメイン名で格納されると予想される場合は、行キーにリバース ドメイン名以外を使用することを検討してください。そうしないと、クラスタ内の単一のノードに大半のデータが書き込まれ、表が過負荷状態になる可能性があります。
文字列識別子
簡単な文字列(たとえば、ユーザー ID など)で識別可能なエンティティに関するデータを格納する場合は、その文字列識別子を行キー、または行キーの一部として使用します。識別子の長さが常に予測可能になるように、識別子そのものではなく識別子のハッシュを使用することを検討してください。ハッシュを使用する方法は、複数の値を単一の行キーとしてまとめる場合(後述)にとりわけ有用です。
タイムスタンプ
データを記録日時に基づいて検索する頻度が高い場合は、行キーの一部にタイムスタンプを含める方法をおすすめします。ただし、タイムスタンプを単独で行キーにする方法は、大半の書き込みが特定の 1 ノードに集中してしまうため、おすすめできません。同じ理由で、タイムスタンプを行キーの先頭に配置するのも避けてください。
たとえば、アプリケーションで、CPU およびメモリの使用状況などのパフォーマンス関連のデータを、多数のマシン上で毎秒記録する必要があるとします。
このようなケースでは、パソコンの識別子とデータのタイムスタンプを連結したもの(例: machine_4223421#1425330757685)をデータの行キーとして使用します。
最新のレコードを最初に検索するのが普通の場合は、行キーにリバース タイムスタンプを使用します。リバース タイムスタンプは、お使いのプログラミング言語の長整数の最大値(Java の場合は java.lang.Long.MAX_VALUE など)からタイムスタンプの値を引くことによって、反転することができます。リバース タイムスタンプを使用すると、新しいレコードが最初にくるようにレコードが並べ替えられます。
単一の行キーを構成する複数の値
行キーは、Cloud Bigtable を効率的に検索する唯一の方法であるため、行キーに複数の識別子を含めると有用な場合がよくあります。行キーに複数の値が含まれている場合は、データの使い方を明確に理解しておくことがとりわけ重要となります。
たとえば、アプリケーションで、ユーザーがメッセージを投稿でき、投稿内でユーザー同士が互いに言及できるとします。このとき、記事内の特定のユーザーにタグを付けているすべてのユーザーを一覧表示する効率的な方法が必要であるとします。この目標を実現する 1 つの方法として、タグ付けされているユーザー名のハッシュと、そのタグ付けを行ったユーザー名のハッシュを連結したものを行キーとして使用する方法があります。各行には、ハッシュ解除されたユーザー名と投稿データが格納されます。
特定のユーザー名にタグ付けしているユーザー名を見つけるには、あるいは、そのユーザー名がタグ付けされているすべての投稿を表示するには、単純に、行キーがそのユーザー名で始まる行範囲を取得すれば済みます。
上の例に示したとおり、行キーを作成する際には、明確な行範囲も検索できるようにしておくことが重要です。そうしないと、クエリを実行すると表スキャンが必要になり、特定の行を検索するよりも大幅に効率が低下してしまいます。たとえば、パフォーマンス関連データを毎秒格納するとします。行キーがタイムスタンプとその後に続くマシン識別子(たとえば、1425330757685#machine_4223421)で構成されている場合、クエリを特定のマシンに絞り込む効率的な方法はありません。タイムスタンプによる絞り込みしかできないことになります。
避けたい行キー
行キーのタイプによっては、データのクエリが難しくなったり、パフォーマンスが低下する可能性があります。このセクションでは、Cloud Bigtable で使用すべきではない行キーのタイプについて説明します。
ドメイン名
標準の非リバース ドメイン名を行キーとして使用しないでください。標準のドメイン名はドメインの一部に属するすべての行を検索するには非効率的です(たとえば、company.com に関連するすべての行が、services.company.com、product.company.com などの異なる行範囲に属するといった場合)。また、標準のドメイン名を使用すると、関連するデータが 1 か所にまとめられない形で行が並べ替えられることになるため、結果として圧縮効率が低下します。
シーケンシャル数値 ID
お使いのシステムで、アプリケーションの各ユーザーに数値 ID が割り当てられているとします。このような場合には、表の行キーとしてユーザーの数値 ID を使いたくなるかもしれません。 しかし、新規のユーザーのほうがアクティブなユーザーになる可能性が高いため、このような方法では、大半のトラフィックがごく少数のノードに集中してしまいます。
より安全な方法として、ユーザーの数値 ID のリバース版を使用する方法があります。この方法なら、Cloud Bigtable 表のすべてのノードに均等にトラフィックが分散されます。
静的な繰り返し更新される識別子
頻繁に更新する必要のある値を識別するために単一の行キーを使用しないでください。たとえば、メモリの使用状況データを毎秒格納する場合に、memusage という名前の単一の行キーを使用して行を繰り返し更新するようなオペレーションを行わないでください。
このような操作を実行すると、使用頻度の高い行が格納されている表が過負荷状態になります。
また、しばらくの間、セルの直前の値が領域を消費したままになるため、行のサイズが上限を超えてしまう可能性があります。
このような場合は、指標のタイプ、区切り文字、タイムスタンプで構成される行キーを使用して、行ごとに 1 つの値を格納します。たとえば、一定期間にわたってメモリの使用状況を追跡するには、memusage#1423523569918 のような行キーを使用します。この方法は効率的です。なぜなら、Cloud Bigtable では、新しい行を作成するのに要する時間は、新しいセルを作成するのに要する時間と大差ないからです。また、この方法を使用すると、適切な開始キーと終了キーを計算することで、特定の日付範囲から迅速にデータを読み取ることができます。
毎分数百回更新されるカウンタなど、頻繁に変化する値の場合は、単純にデータをアプリケーション層でメモリ内に保持し、新しい行を定期的に Cloud Bigtable に書き込むのが最善の方法です。
列ファミリーと列修飾子
このセクションでは、表内の列ファミリーと列修飾子についての考え方を説明します。
列ファミリー
Cloud Bigtable では、HBase と違って、最大で 100 の列ファミリーを使用できます。しかも、高いパフォーマンスは維持されたままです。ですから、互いに関連する複数の値を行に格納する場合はいつでも、それらの値を 1 つの列ファミリーにまとめることをおすすめします。列ファミリー内にデータをまとめることにより、単一または複数のファミリーからデータを取得できます。各行内のデータをすべて取得する必要はありません。データをできるだけ近接した場所にまとめることで、最も頻繁に呼び出される API で必要な情報のみを取得することができます。
また、列ファミリー名は、リクエストのたびに転送データに含められるため、列ファミリーには短い名前を付けるようにしてください。
列修飾子
Cloud Bigtable 表はスパースであるため、行ごとに必要なだけ列修飾子を作成することができます。行内の空のセルによって領域が消費されることはありません。このため、列修飾子をデータのように扱うのが理にかなっていることがよくあります。たとえば、表にユーザーの投稿を格納する場合、各投稿の一意の識別子を列修飾子として使用できます。
行キーと列ファミリーについては、列修飾子の名前を短くすることをおすすめします。これにより、リクエストごとに転送されるデータ量を最小限に抑えることができます。
縦長の表と横長の表
ここまでの例では、横長の表について見てきました。つまり、1 つの行に多数の列が含まれている表です。列修飾子をデータとして使用する場合は通常、横長の表になります。これに対して、縦長の表(つまり列数が非常に少ない行が多数含まれる表)を使用したほうがよいケースもあります。
ここで、概要のセクションで示した Prezzy(合衆国大統領のソーシャル ネットワーク)の例を再掲します。
この例は、各ユーザーがフォローしているユーザーを追跡する横長の表です。この表には、各ユーザーについて、そのユーザーにフォローされているユーザーごとに 1 つの列が対応しています。
ここで、各ユーザーがフォローしているユーザーの全リストまでは必要ない場合を考えてみます。 つまり、全リストではなくもっと絞り込んだ質問に対する答えが得られればよいという場合です。具体的には、「ユーザー A はユーザー B をフォローしているか?」といった質問です。
このようなケースの効率的な解決策として、縦長の表を使用する方法があります。つまり、フォローしている側とフォローされている側の 1 ペアごとに 1 行が対応する表です。この縦長の表の行キーは、フォローしている側のユーザー名のハッシュとフォローされている側のユーザー名のハッシュを連結したものです。以上により、単一の行キーを検索することで、ユーザー A がユーザー B をフォローしているかどうかを確認できます。目的の行が存在していれば、ユーザー A はユーザー B をフォローしており、目的の行が存在しなければ、ユーザー A はユーザー B をフォローしていないことになります。
以下に、Prezzy データの縦長バージョンを示します。
ユーザー jadams が tjefferson をフォローしているかどうかを知りたければ、jadams のハッシュ(df887e44)と tjefferson のハッシュ(b0452e5c)を連結した行キーを検索するだけです。行 df887e44b0452e5c が存在していれば、jadams は tjefferson をフォローしています。
縦長の表には、フォローされているユーザーのユーザー名が格納されている点に注目してください。これがないと、ハッシュに対応するユーザー名を見つけるのに長い時間が必要になってしまいます。
参考
時間の経過とともに追跡されるデータを格納する場合は、時系列データのスキーマ設計をご覧ください。