Share via


Azure DocumentDB においてマルチテナント アプリケーションの規模を拡大する

このポストは、12 月 3 日に投稿された Scaling a Multi-Tenant Application with Azure DocumentDB の翻訳です。

テナント配置と負荷分散のベスト プラクティス

はじめに

Azure DocumentDB 上にどのようにマルチテナント アプリケーションを設計すればよいのか」という質問をよく受けます。これには多くの答えがありますが、最適な答えがどれかはそれぞれのアプリケーションのシナリオによって異なります。

大まかに言うと、テナントの規模に応じて、データベース単位で分割するか、コレクション単位で分割するかを選択します。テナント データが比較的小さく、テナント数が比較的多い場合は、アプリケーションに必要な全体のリソースを少なくするために、同一のコレクション内に複数のテナントのデータを保管することをお勧めします。テナントはドキュメントのプロパティで識別でき、テナント固有のデータはフィルター クエリを発行して取得できます。DocumentDB のユーザーとアクセス許可 (英語) を使用して、テナント データを分離したり、承認キーを通じてリソースレベルでアクセスを制限したりすることもできます。

テナントが比較的大きい場合や、専用のリソースを必要とする場合、またはさらなる分離が必要な場合は、各テナントに専用のコレクションまたはデータベースを割り当てることができます。いずれの場合も、アプリケーションのデータ ストアを拡大する際の面倒な処理の大部分は DocumentDB によって自動で行われます。

この記事では、アプリケーションの要件に合わせて優れた設計を行うための考え方と戦略を説明します。

コレクションとは

データのパーティション分割について深く踏み込む前に、コレクションとは何か、何がほかと違うのかを理解しておくことが重要です。

既にご存知かもしれませんが、コレクションは、JSON ドキュメントのコンテナーです。コレクションは、DocumentDB でクエリおよびトランザクションを実行するうえでのパーティションの単位および境界でもあります。各コレクションは、予約されている量のスループットを提供します。アプリケーションを拡大するには、ストレージとスループットのどちらを拡大する場合も、コレクションを追加してそれらにドキュメントを分散します。

まず、必ず理解していただきたいのは、コレクションはテーブルではないという点です。コレクションはスキーマを強制しません。つまり、異なるスキーマを持つさまざまな種類のドキュメントを同じコレクションに保管できます。種類の異なるエンティティも、ドキュメントに「type」属性を追加するだけで管理できます。

他のドキュメント データベースとは異なり、規模の拡大やパフォーマンス向上のために多数のコレクションを持ってもまったく問題ありません。ただし、コスト削減のためにコレクションを再利用するほうがよいでしょう。

DocumentDB によるシャーディング

DocumentDB アプリケーションは、データを水平方向にパーティション分割することで、ストレージとスループットの両面においてほぼ無限に拡大できます。この考え方は、一般的にシャーディングと呼ばれます。

シャーディングは、大規模な Web アプリケーションで高いスケーラビリティを実現するための標準的なアプリケーション パターンです。トランザクション処理のメリットを利用するアプリケーションでは、キーに基づいてデータを慎重に分割する必要があり、各パーティションはトランザクション ドメイン (つまり、コレクション) に収まるように十分に小さくなければなりません。シャーディングされたアプリケーションは、データが複数のパーティションまたはトランザクション ドメインにわたって分散されていることを認識する必要があり、シャード キーに基づいてパーティションを選択し、そのパーティションとやり取りするためのロジックも必要となります。

 複数のデータベースおよびコレクションにシャーディングされた DocumentDB アプリケーション

パーティション分割の単位

複数のトランザクション ドメインにシャーディングを行うべきかどうかの判断は、アプリケーションの規模によって変わってきます。一般に、シャーディングを行うとアプリケーションにはデータ管理用のロジックが新たに必要になります。

 

1 つのデータベース アカウント下の階層型リソース モデル

それでは、DocumentDB のリソース モデルと、データをパーティション分割する際のさまざまな単位を詳しく説明しましょう。

すべてのテナントを 1 つのコレクションに配置する: これは一般的に、ほとんどのアプリケーションの最初の形態として適しています。たとえば、すべてのテナントの総リソース消費量が、ストレージとスループットの両方で 1 つのコレクションの制限内に収まる場合などです。

セキュリティは、ドキュメント内にテナント プロパティを追加し、すべてのクエリにテナント フィルターを適用する (たとえば、SELECT * FROM collection c WHERE c.userId = "Andrew") だけで、アプリケーションレベルで実施できます。また、セキュリティを DocumentDB レベルで実施することもできます。この場合、テナントごとにユーザーを作成しテナント データへのアクセス許可を割り当て、ユーザーの承認キーを使用してテナント データにクエリを行います。

テナント データを 1 つのコレクション内に保管することの大きなメリットとしては、複雑さを回避できる、アプリケーション データ全体でトランザクション処理を確実にサポートできる、ストレージの金銭的コストを最小限に抑えられるといった点が挙げられます。

テナントを複数のコレクションに配置する: アプリケーションがストレージまたはスループットについて 1 つのコレクションで処理できる以上のキャパシティを必要とする場合は、データを複数のコレクションに分割できます。データベースのサイズやコレクションの数に制限を設ける必要はありません。DocumentDB では、アプリケーションの拡大に合わせて、動的にコレクションやキャパシティを追加できます。ただし、テナント データの分割や要求のルーティングをアプリケーションがどのように行うのかを決める必要があります。これについては、後ほど説明します。

データを複数のコレクションに分割することのメリットは、リソースのキャパシティを増やせるという点のほかに、大きなテナントを密度の低いコレクションに配置することによって、その大きなテナントに他の小さなテナントより多くのリソースを割り当てることが可能になるという点があります。たとえば、スループットを占有する必要のあるテナントは、そのテナントだけのコレクションに配置するということができます。

テナントを複数のデータベースに配置する: 複数のデータベースにテナントを配置すると、概ね複数のコレクションにテナントを配置した場合とほぼ同じように機能します。上記のリソース モデルの図にあるように、DocumentDB のコレクションとデータベースの大きな違いは、ユーザーとアクセス許可のスコープがデータベースであるという点です。つまり、各データベースには専用のユーザーとアクセス許可のセットがあるので、これらを使用して、それぞれのコレクションおよびドキュメントを分離することができます。

テナントをデータベース単位で分離すると、テナントがきわめて多くのユーザーとアクセス許可を必要とするシナリオでも、ユーザーとアクセス許可の管理が容易になります。また、テナントがデータベース単位で分離されており、テナントがアプリケーションを廃止した場合、データベースを削除するたけで、そのテナントのデータと一緒に関連するユーザーおよびアクセス許可もまとめて破棄できます。

テナントを複数のデータベース アカウントに配置する: テナントを複数のデータベース アカウントに配置する場合も、テナントを複数のデータベースに配置する場合とほぼ同じように機能します。データベース アカウントとデータベースの大きな違いは、マスター/セカンダリ キー、DNS エンドポイント、および請求のスコープがデータベース アカウントであるという点です。

テナントをデータベース アカウント単位で分離すると、テナントがそれぞれ専用のデータベース アカウント キーへのアクセスを必要とするシナリオ (たとえば、テナントが自身専用のコレクションとユーザーのセットを作成、削除、管理する機能を必要とする場合など) で、アプリケーションがテナント間で独立を保つことができます。

よく使用されるシャーディング パターン

データを複数のトランザクション ドメインにパーティション分割する場合、アプリケーションが正しいパーティションにデータを分割し、要求をルーティングするには、ある種の経験則が必要になります。データのパーティション分割の方法としては、経験的に以下のようなものがよく利用されます。

範囲パーティション分割

パーティション分割キーが特定の範囲内にあるかどうかに基づいてパーティションを割り当てます。たとえば、タイムスタンプや地理的情報 (郵便番号が 30000 から 39999 の範囲であるかどうかなど) でデータを分割することができます。

 

月ごとの範囲パーティション分割

検索パーティション分割

パーティションに関連付いた個別の値を格納している検索ディレクトリに基づいてパーティションを割り当てます。これは通常、どのデータをどのパーティションに保管するかを管理する検索マップを作成することによって実装します。検索結果をキャッシュすることによって、受け渡しの繰り返しを避けることもできます。たとえば、データをユーザーごと (パーティション G は Harry、Ron、および Hermione を格納、パーティション S は Draco、Vincent、および Gregory を格納など)、または国ごと (パーティション Scandinavia はノルウェー、デンマーク、スウェーデンを格納など) に分割できます。

 

検索パーティション分割

ハッシュ パーティション分割

ハッシュ関数の値に基づいてパーティションを決定します。この方法では、任意の個数のパーティションにデータを均等に分散することができます。たとえば、テナントのハッシュ コード % 3 でデータを分割し、テナントを 3 つのパーティションに均等に分散することができます。

ハッシュ パーティション分割

皆様の場合にはどのパーティション分割の戦略が適切でしょうか。範囲パーティション分割は、一般的に、日付が関係する場合に便利です。時間の経過に応じてパーティションを割り当てる手軽で自然なメカニズムを作成できます。検索パーティション分割では、データの性質上は決まった順序や関連性のないデータをグループ化したり整理したりすることができます。たとえば、組織ごとにテナントをグループ化したり、地域別に州をグループ化したりすることが可能です。ハッシュ パーティション分割は負荷分散を行う際に非常に有用です。特定グループのデータ量の予測や手動での均等化が難しい場合、または範囲パーティション分割でデータが予想外に集中してしまう場合に役立ちます。

パーティション分割の方法は 1 つだけに絞る必要はありません。シナリオによっては、上記の方法を組み合わせることも効果的です。たとえば、範囲と検索のパーティション分割を組み合わせれば、範囲パーティション分割による管理のしやすさと、検索パーティション分割による明示的な制御を兼ね備えることができます。

 

範囲と検索を組み合わせたパーティション分割

ファンアウト操作

複数のトランザクション ドメインにわたるデータを操作する方法としてよく使用されるのがファンアウト クエリです。このクエリによって、アプリケーションは各パーティションに並列でクエリを実行し、その結果を統合します。

 

ファンアウト クエリ

アーキテクチャの決定で重要になるのはファンアウトをいつ行うかです。例として、ニュースフィードを集約するタイムラインを考えてみましょう。ファンアウトには以下のような方策が考えられます。

書き込み時ファンアウト: これは、書き込み時にほとんどの処理を行っておき、読み取り時の計算を避けようとする考え方です。読み取り時はアクセスがきわめて高速となり、操作も容易になります。これは、タイムラインが Twitter のようなものである場合 (ほぼ永続的で、追加されるのみのソース フィードがユーザーごとに集約される場合) に適しています。受信イベントが記録されるときに、ソース フィードへの保管が行われ、さらにそのイベントが表示される各タイムラインにコピーを含むレコードが作成されます。

読み取り時ファンアウト: これは、タイムラインが Facebook のようなものである場合 (フィードは一時的で、リアルタイムのプライバシー チェックやコンテンツ加工など動的な機能をサポートする必要がある場合) に適します。イベントが記録されるときはソース フィードへの保管のみが行われます。ユーザーが各自のタイムラインを要求するときに、システムは毎回、ユーザーが表示できるすべてのソース フィードに対してファンアウト読み取りを行い、それらのフィードを 1 つに集約します。

ストレージとスループットの管理

パーティションがいっぱいになりリソースが足りなくなり始めたら、データを別のパーティションに移したり再分散したりする時期です。このような状況は、小さかったテナントが想定よりも多くのストレージやスループットを消費するようになった場合や、単にアプリケーションが既存のパーティション以上に拡大している場合に発生します。リソース消費の割合に基づいて適切なしきい値 (たとえば、ストレージの 80% が消費されたなど) を決めて監視しておけば、アプリケーション エラーが発生する危険性を回避するための対策を講じることができます。

 

サービスの中断を避けるために、十分な余裕を持ってデータの移動や再分散を行う

ストレージの制約を解決する 1 つの方法として、後続の受信データを新しいコレクションに格納することができます。たとえば、コレクションが時間的性質の強いデータを保管しており、月や日でパーティション分割を行っている場合に最適です。このような場合は、時間が経過して古くなったコレクションを削除またはアーカイブすることも検討できます。コレクションはトランザクションおよびクエリの境界であることに注意してください。複数のコレクションにわたるデータの操作には、読み取りや書き込みのファンアウトが必要になる場合があります。

ストレージとスループットのどちらか、または両方の制約を解決するもう 1 つの方法は、テナント データの再分散です。たとえば、小さかったテナントがその他のテナントよりも多くのスループットを消費するようになった場合は、そのテナントを別のコレクションに移動できます。移行処理中は、特定のテナントのサービスの中断を避けるために、読み取りまたは書き込みのファンアウトが必要になる場合があります。

まとめ

DocumentDB は、マイクロソフト社内においても、ここで説明したさまざまなマルチテナントのシナリオを拡大できることが証明されています。たとえば、MSN ではヘルスケアのエクスペリエンス (Web およびモバイル) のユーザー データをシャーディングされたデータベース アカウントに保管しています。この使用例に関するブログ記事をぜひご覧ください。

ほとんどの事柄について、アプリケーションの設計はシナリオとデータ アクセス パターンによって大きく変わってきます。この記事が適切な方向へ進むための出発点となれば幸いです。

DocumentDB の利用開始の方法や、その他の情報については、こちらの Web サイトを参照してください。

Microsoft Azure でのマルチテナント アプリケーション開発全般に関する情報については、MSDN の記事を参照してください。