Azure Cosmos DB パーティション分割デザイン パターン – パート1 (Azure Cosmos DB partitioning design patterns – Part 1)

Azure Cosmos DB partitioning design patterns – Part 1


Rafat Sarosh (Principal Program Manager, Azure Cosmos DB)

この記事では、データを効率的に分散し、アプリケーションのパフォーマンスを改善し、より高速なルックアップを可能にするための、パーティション キーの使い方を学びます。この記事の前提条件は、Azure Cosmos DBの一般知識と、変更フィード要求ユニット (RU)、Azure Functionsの十分な理解です。

高いスループットで挿入したいデータを持っており、2つ以上の異なるキーでクエリすることを想像してみましょう。このシナリオでは、あなたが航空会社に勤めており、ユーザーの予約情報をコレクションに格納する必要がある、と仮定します。ユーザー データは、次のように定義されます。

{
    UserId: user@email.com,
    FirstName: John,
    LastName: Doe,
    PNR: 2345423,
    CityOfOrigin: Seattle,
    CityOfDestination: London,
    DOB: 12.2.76,
    他の詳細…
}

あなたは、パーティション キーとして、多くの可能な値から「UserId」(ユーザーの電子メール アドレス) を選択します。「UserId」は各ユーザーに固有のものであり、これによってデータが十分に分散されるようになるので、これはパーティション キーとして良い選択です。図1に示したように、データは、すべてのパーティションにわたって均等に分散されます。しかし、データをクエリする際に、常に「UserId」が分かっているわけではありません。ユーザーの姓やユーザーのPNR (旅客予約記録) を使って、データをクエリしたい場合もあります。

図1:パーティションにわたって均等に分散されたデータ

図1:パーティションにわたって均等に分散されたデータ

Azure Cosmos DBは、既定ですべてのデータにインデックスを作成します。「LastName」でデータをクエリしようとする場合、結果を得られますが、パーティション キーのないクエリはファンアウト クエリになるので、より多くの要求ユニット (RU/s) がかかります。ファンアウト クエリはすべてのパーティションを確認します。これには余分なRU/sがかかり、アプリケーションのパフォーマンスに影響を与えることがあります。データが少なく、パーティション数も少ない場合は、ファンアウト クエリの大幅な副作用に気づかないことがありますが、多数のパーティション、大量のデータを持つようになり始めると、ファンアウト クエリがアプリケーションに弊害をもたらすことがあります。頻度の低いクロス パーティション クエリは問題ありませんが、それが頻度の高いクエリの場合、解決策は何でしょうか?

1つの選択肢は、「PNR」から「UserId」へのマッピングのための「PNR」コレクション、「LastName」から「UserId」へのマッピングのための「LastName」コレクションという、2つのルックアップ コレクションを持つことです。「PNR」コレクションは、パーティション キー、行キーとして「PNR」を持ち、値として「UserId」を持ちます。

3つのコレクション

これらの異なるルックアップ コレクションは、アプリケーションをより効率的にします。PNRで詳細をルックアップするには、まず、「UserId」を取得するために、「PNR」コレクションをクエリします。それから、「UserId」を使って、「User」コレクションをクエリします。これら2回の呼び出しは数ミリ秒以内に完了でき、単一のファンアウト クエリより少ないRU/sを消費します。ほとんどのポイント ルックアップ クエリは、1-2ミリ秒以内に完了できます。2回のルックアップであっても、ほとんどのクエリは10ミリ秒以内に完了できます。

呼び出しに余計な数ミリ秒を追加したくなく、代わりに「PNR」コレクション、「LastName」コレクションでデータを複製することにするかもしれません。これは高速なルックアップを可能にしますが、データの更新時に複雑さとコストが加わることがあるので、これは推奨されません。結局は、要件、パフォーマンス、複雑さのバランスを取らなければなりません。多くの場合、単純な解決策から始めることが最高のアプローチです。

ここで、異なるコレクションにおけるデータの分散について見てみましょう。たとえば「LastName」コレクションを見てみると、姓が「Smith」の人は姓が「Zubrkee」の人より多いので、データは均等に分散されないことが分かります。この場合、データは図2のようになります。

図2:パーティションにわたって不均等に分散されたデータ

図2:パーティションにわたって不均等に分散されたデータ

このシナリオでは、データは不均等に分散されており、満杯のパーティションもあれば、十分に使われていないパーティションもあります。

  • コレクション全体のRU/sは、すべてのパーティションにわたって分割されます。これは、1,000 RU/sが5つのパーティションにわたって分散され、各パーティションが200 RU/sを持つことを意味しています。これらのパーティションのいずれかに200 RU/s以上の呼び出しを行おうした場合、閾値を超えるので、呼び出しが失敗し始めます。開発者が、コレクション レベルで1,000 RU/sを割り当てられているにもかかわらず、200 RU/sでスロットルされていることに気付いた場合は大抵、問題は、悪いパーティション キーのために、1つのパーティションだけにアクセスしていることです。
  • 現在、パーティションは、最大10 GBのデータを持つことができます (今後、変更される可能性があります)。このため、すべてのパーティションを効率的に満たすパーティション キーを使うことが、重要になります。「LastName」の例では、データを均等に分散するためには、より粒度の細かいパーティション キーが必要です。データが「CityOfOrigin」も含んでいるので、「LastName」と「CityOfOrigin」から新しいパーティション キーを作ることができます。結果は、次の図3のようになります。

図3:粒度の細かいパーティション キーを適用した後のデータ分散

図3:粒度の細かいパーティション キーを適用した後のデータ分散

これは、ずっと良いように見えます。データがより均等に分散しており、旅行者は、自分の姓と出発地を入力するだけで、簡単かつ迅速に自分の予約をルックアップできます。

データを均等に分散したので、次は、どのようにして他のコレクションにデータを入力するのでしょうか? このためには、変更フィードを理解する必要があります。変更フィードは、コレクション内で起こっている内部変更のすべてを公開します。Azure Cosmos DBにおける変更フィードのサポートは、あらゆる変更のためにAzure Cosmos DBコレクションをリッスンすることで、動作します。変更フィードは、変更されたドキュメントの、変更された順序でソート済みのリストを出力します。変更を永続化して、非同期で漸進的に処理できます。並列処理のために、出力を1つ以上のコンシューマーにわたって分散できます。

ドキュメント コレクション内の各パーティション キー範囲に対して、変更フィードを利用可能なので、並列処理のために、1つ以上のコンシューマーにわたって変更フィードを分散できます。「User」コレクションにレコードが挿入されるたびに、そのレコードが変更フィードに現れます。変更フィードを利用する最も簡単な方法は、Azure Functionsです。Azure Functionsは、インフラストラクチャを明示的にプロビジョニング、管理する必要なしにオンデマンドでコードを実行できる、サーバーレス コンピューティング サービスです。Azure Functionsを使って、多様なイベントに応じてスクリプトやコードを実行します。

変更フィードとAzure Functions

Azure Functionsを通して変更フィードを利用すると、挿入/変更されたすべてのドキュメントが、関数のパラメーターとして自分の関数に届きます。

public static async Task Run(IReadOnlyList<Document> input, TraceWriter log)

自分の関数でドキュメント全体を入手したら、それに応じて「PNR」コレクション、「LastName」コレクションを更新できます。

Azure Cosmos DB、Azure Functions、変更フィードの使い方についてさらに学ぶには、このスクリーンキャストを見てください。または、変更フィードについて読んでください。