現実の例を使用して Azure Cosmos DB のデータをモデル化およびパーティション分割する方法

適用対象: NoSQL

この記事では、データ モデリングパーティション分割プロビジョニング済みスループットなどの Azure Cosmos DB のいくつかの概念を基にして、現実世界のデータ設計に取り組む方法を示します。

通常、リレーショナル データベースで作業している場合は、データ モデルの設計方法についての習慣や直感が築かれているはずです。 Azure Cosmos DB に固有の制約のため (それは Azure Cosmos DB だけが持つ長所でもありますが)、それらのベスト プラクティスのほとんどはうまく流用できず、次善のソリューションになってしまうことがあります。 この記事の目的は、項目のモデリングからエンティティのコロケーションやコンテナーのパーティション分割まで、Azure Cosmos DB で現実のユース ケースをモデル化する完全なプロセスの手順を示すことです。

この記事の概念を示すコミュニティで作成されたソース コードは、こちらでダウンロードまたは表示できます。

重要

コミュニティの共同作成者がこのコード サンプルを提供したのであり、Azure Cosmos DB チームはそのメンテナンスをサポートしていません。

シナリオ

この演習では、"ユーザー" が "投稿" を作成できるブログ作成プラットフォームのドメインについて考えます。 また、ユーザーは "いいね! " を付けたり、投稿に "コメント" を追加したりすることもできます。

ヒント

"斜体" で強調されているいくつかの単語は、モデルで操作する必要がある "モノ" の種類を示します。

仕様への要件の追加:

  • フロント ページには、最近作成された投稿のフィードが表示されます。
  • 1 人のユーザーのすべての投稿、1 つの投稿に対するすべてのコメント、1 つの投稿に対するすべてのいいね! をフェッチできます。
  • 投稿と共に、作成者のユーザー名および投稿に付いているコメントといいね! の数が返されます。
  • コメントといいね! については、それを作成したユーザーのユーザー名も返されます。
  • 一覧表示するときは、投稿の内容の切り詰められた概要だけを表示する必要があります。

主要なアクセス パターンを明らかにする

最初に、ソリューションのアクセス パターンを明らかにすることにより、初期の仕様に構造を提供します。 Azure Cosmos DB のデータ モデルを設計するときは、モデルで対応する必要がある要求を理解し、モデルがそれらの要求に効率的に対応できることを確認することが重要です。

全体的なプロセスをわかりやすくするため、CQRS のボキャブラリを借りて、さまざまな要求をコマンドまたはクエリに分類します。 CQRS では、コマンドは書き込み要求 (つまり、システムを更新する意図) であり、クエリは読み取り専用の要求です。

プラットフォームで公開される要求の一覧を次に示します。

  • [C1] ユーザーを作成/編集する
  • [Q1] ユーザーを取得する
  • [C2] 投稿を作成/編集する
  • [Q2] 投稿を取得する
  • [Q3] ユーザーの投稿を短い形式で一覧表示する
  • [C3] コメントを作成する
  • [Q4] 投稿のコメントを一覧表示する
  • [C4] 投稿に "いいね!" を付ける
  • [Q5] 投稿のいいね! を一覧表示する
  • [Q6] 最近作成された x 件の投稿を短い形式で一覧表示する (フィード)

この段階では、各エンティティ (ユーザー、投稿など) の内容の詳細については考えていません。 これは、通常、リレーショナル ストアについて設計するときに最初に取り組むステップの 1 つです。 これらのエンティティをテーブル、列、外部キーなどの観点から変換する方法を把握する必要があるので、このステップを最初に開始します。書き込み時にスキーマが適用されないドキュメント データベースでは、これはそれほど重要なことではありません。

最初からアクセス パターンを明らかにすることが重要である主な理由は、この要求の一覧がテスト スイートになるためです。 データ モデルの作業を繰り返すたびに、各要求を検討してパフォーマンスとスケーラビリティを確認します。 各モデルで使用される要求ユニットを計算して最適化します。 これらのすべてのモデルでは、既定のインデックス作成ポリシーが使用されますが、これは特定のプロパティのインデックスを作成することでオーバーライドすることができ、それによって RU の使用量と待機時間をさらに向上させることができます。

V1: 最初のバージョン

2 つのコンテナー usersposts から始めます。

users コンテナー

このコンテナーにはユーザー項目のみが格納されます。

{
    "id": "<user-id>",
    "username": "<username>"
}

このコンテナーは id でパーティション分割します。つまり、そのコンテナー内の各論理パーティションには、1 つの項目だけが格納されます。

posts コンテナー

このコンテナーでは、投稿、コメント、いいね! などのエンティティをホストします。

{
    "id": "<post-id>",
    "type": "post",
    "postId": "<post-id>",
    "userId": "<post-author-id>",
    "title": "<post-title>",
    "content": "<post-content>",
    "creationDate": "<post-creation-date>"
}

{
    "id": "<comment-id>",
    "type": "comment",
    "postId": "<post-id>",
    "userId": "<comment-author-id>",
    "content": "<comment-content>",
    "creationDate": "<comment-creation-date>"
}

{
    "id": "<like-id>",
    "type": "like",
    "postId": "<post-id>",
    "userId": "<liker-id>",
    "creationDate": "<like-creation-date>"
}

このコンテナーは postId でパーティション分割します。つまり、そのコンテナー内の各論理パーティションには、1 つの投稿、その投稿に対するすべてのコメント、およびその投稿に対するすべてのいいね! が格納されます。

このコンテナーでホストされるエンティティの 3 つの種類を区別するため、このコンテナーに格納される項目に type プロパティを導入しました。

また、関連データについては埋め込みではなく参照を選択してあります (これらの概念について詳しくは、こちらのセクションを確認してください)。これは次のような理由によるものです。

  • ユーザーが作成できる投稿の数には上限がない。
  • 投稿の長さは任意である。
  • 投稿が保持できるコメントといいね! の数に上限はない。
  • 投稿自体を更新することなく、投稿にコメントまたはいいね! を追加できるようにしたい。

モデルのパフォーマンスの確認

ここでは、最初のバージョンのパフォーマンスとスケーラビリティを評価します。 前に識別した要求ごとに、その待機時間と、消費される要求ユニットの数を測定します。 この測定に使用するダミー データ セットには、100,000 人のユーザーが含まれ、1 ユーザーあたり 5 ~ 50 件の投稿、1 投稿あたり最大 25 個のコメントと 100 個のいいね! が含まれます。

[C1] ユーザーを作成/編集する

この要求は、users コンテナーの項目を作成または更新するだけなので、簡単に実装できます。 id パーティション キーのおかげで、要求はすべてのパーティションにうまく分散します。

ユーザーのコンテナーに 1 つの項目を書き込む図。

待機時間 RU 料金 パフォーマンス
7 ms 5.71 RU

[Q1] ユーザーを取得する

ユーザーの取得は、users コンテナーから対応する項目を読み取ることによって行われます。

ユーザーのコンテナーから 1 つの項目を取得する図。

待機時間 RU 料金 パフォーマンス
2 ms 1 RU

[C2] 投稿を作成/編集する

[C1] と同様に、posts コンテナーに書き込むことだけが必要です。

投稿コンテナーに 1 つの投稿項目を書き込む図。

待機時間 RU 料金 パフォーマンス
9 ms 8.76 RU

[Q2] 投稿を取得する

最初に、posts コンテナーから対応するドキュメントを取得します。 ただし、それだけでは不十分です。仕様では、投稿の作成者のユーザー名、投稿に対するコメントの数、いいね! の数を集計する必要もあります。 一覧表示されている集計では、さらに 3 つの SQL クエリを発行する必要があります。

投稿の取得と追加データの集計の図。

その他のクエリごとに、それぞれのコンテナーのパーティション キーでフィルター処理を行います。これこそが、パフォーマンスとスケーラビリティを最大にする必要がある部分です。 ただし、最終的には 1 つの投稿を返すために 4 つの操作を実行する必要があるので、その改善は次の繰り返しで行います。

待機時間 RU 料金 パフォーマンス
9 ms 19.54 RU

[Q3] ユーザーの投稿を短い形式で一覧表示する

最初に、その特定のユーザーに対応する投稿をフェッチする SQL クエリを使用して、目的の投稿を取得する必要があります。 ただし、作成者のユーザー名およびコメントといいね! の数を集計するため、クエリをさらに発行する必要があります。

1 人のユーザーのすべての投稿を取得し、追加データを集計する図。

この実装では、多くの欠点が示されています。

  • 最初のクエリによって返される各投稿に対し、コメントといいね! の数を集計するクエリを発行する必要があります。
  • メインのクエリでは posts コンテナーのパーティション キーによるフィルター処理が行われないので、ファンアウトとパーティション スキャンがコンテナー全体に対して行われます。
待機時間 RU 料金 パフォーマンス
130 ms 619.41 RU

[C3] コメントを作成する

コメントは、対応する項目を posts コンテナーに書き込むことによって作成されます。

投稿コンテナーに 1 つのコメント項目を書き込む図。

待機時間 RU 料金 パフォーマンス
7 ms 8.57 RU

[Q4] 投稿のコメントを一覧表示する

最初に、やはりその投稿のすべてのコメントをフェッチするクエリを実行し、コメントごとに個別にユーザー名を集計する必要もあります。

1 つの投稿に対するすべてのコメントを取得し、追加データを集計する図。

メイン クエリはコンテナーのパーティション キーでフィルター処理されますが、ユーザー名の個別集計は全体的なパフォーマンスに悪影響を与えます。 それについては後に改善します。

待機時間 RU 料金 パフォーマンス
23 ms 27.72 RU

[C4] 投稿に "いいね!" を付ける

[C3] と同じように、posts コンテナーに対応する項目を作成します。

投稿コンテナーに 1 つの (類似した) 項目を書き込む図。

待機時間 RU 料金 パフォーマンス
6 ms 7.05 RU

[Q5] 投稿のいいね! を一覧表示する

[Q4] と同じように、その投稿に対するいいね! のクエリを実行した後、それらのユーザー名を集計します。

1 つの投稿に対するすべてのいいね! を取得し、追加データを集計する図。

待機時間 RU 料金 パフォーマンス
59 ms 58.92 RU

[Q6] 最近作成された x 件の投稿を短い形式で一覧表示する (フィード)

作成日の降順に並べ替えられた posts コンテナーのクエリを実行することによって最新の投稿をフェッチした後、各投稿のユーザー名およびコメントといいね! の数を集計します。

最新の投稿を取得し、追加データを集計する図。

やはり、最初のクエリでは posts コンテナーのパーティション キーによるフィルター処理は行われないので、コストのかかるファンアウトが発生します。この場合は、大きい結果セットを対象とし、結果を ORDER BY 句で並べ替えるためさらに悪くなり、要求ユニットに関するコストがさらに高くなります。

待機時間 RU 料金 パフォーマンス
306 ms 2063.54 RU

V1 のパフォーマンスの検討

前のセクションで直面したパフォーマンスの問題を調べると、問題には主に 2 つのクラスがあることがわかります。

  • 一部の要求では、必要なすべてのデータを収集するため、複数のクエリを発行する必要があります。
  • 一部のクエリでは、対象のコンテナーのパーティション キーでフィルター処理されていないため、スケーラビリティを損なうファンアウトが発生します。

これらの問題を解決しましょう。まずは 1 つ目です。

V2: 非正規化を導入して読み取りクエリを最適化する

一部のケースでさらに要求を発行する必要があるのは、最初の要求の結果に取得する必要があるすべてのデータが含まれていないためです。 Azure Cosmos DB のような非リレーショナル データ ストアを使用する場合、データを非正規化することによって、データ セット全体でのこの種の問題が解決します。

この例では、投稿項目を変更し、投稿の作成者のユーザー名、コメントの数、いいね! の数を追加します。

{
    "id": "<post-id>",
    "type": "post",
    "postId": "<post-id>",
    "userId": "<post-author-id>",
    "userUsername": "<post-author-username>",
    "title": "<post-title>",
    "content": "<post-content>",
    "commentCount": <count-of-comments>,
    "likeCount": <count-of-likes>,
    "creationDate": "<post-creation-date>"
}

また、コメント項目といいね! 項目も変更し、それらを作成したユーザーのユーザー名を追加します。

{
    "id": "<comment-id>",
    "type": "comment",
    "postId": "<post-id>",
    "userId": "<comment-author-id>",
    "userUsername": "<comment-author-username>",
    "content": "<comment-content>",
    "creationDate": "<comment-creation-date>"
}

{
    "id": "<like-id>",
    "type": "like",
    "postId": "<post-id>",
    "userId": "<liker-id>",
    "userUsername": "<liker-username>",
    "creationDate": "<like-creation-date>"
}

コメントといいね! の数の非正規化

実現したいのは、コメントまたはいいね! を追加するたびに、対応する投稿の commentCount または likeCount も増分することです。 postId によって posts コンテナーがパーティション分割されているので、新しい項目 (コメントまたはいいね!) とその対応する投稿は、同じ論理パーティション内に存在します。 その結果、ストアド プロシージャを使用して、その操作を実行できます。

コメント ([C3]) を作成するとき、posts コンテナーに新しい項目を追加するだけでなく、そのコンテナーに対して次のストアド プロシージャを呼び出します。

function createComment(postId, comment) {
  var collection = getContext().getCollection();

  collection.readDocument(
    `${collection.getAltLink()}/docs/${postId}`,
    function (err, post) {
      if (err) throw err;

      post.commentCount++;
      collection.replaceDocument(
        post._self,
        post,
        function (err) {
          if (err) throw err;

          comment.postId = postId;
          collection.createDocument(
            collection.getSelfLink(),
            comment
          );
        }
      );
    })
}

このストアド プロシージャは、投稿の ID と新しいコメントの本文をパラメーターとして受け取り、次のことを行います。

  • 投稿を取得します
  • commentCount を増分します
  • 投稿を置き換えます
  • 新しいコメントを追加します

ストアド プロシージャはアトミック トランザクションとして実行されるので、commentCount の値とコメントの実際の数が常に同期します。

新しいいいね! を追加するときも似たストアド プロシージャを呼び出して、likeCount を増分します。

ユーザー名の非正規化

ユーザー名の場合は、ユーザーが異なるパーティションに存在するだけでなく、異なるコンテナーに存在しているので、別のアプローチが必要になります。 パーティションとコンテナー間でデータを非正規化する場合は、ソース コンテナーの変更フィードを使用できます。

この例では、users コンテナーの変更フィードを使用して、ユーザーが自分のユーザー名を更新するたびに対応します。 それが発生したら、posts コンテナーで別のストアド プロシージャを呼び出すことによって、変更を反映します。

posts コンテナーにユーザー名を非正規化する図。

function updateUsernames(userId, username) {
  var collection = getContext().getCollection();
  
  collection.queryDocuments(
    collection.getSelfLink(),
    `SELECT * FROM p WHERE p.userId = '${userId}'`,
    function (err, results) {
      if (err) throw err;

      for (var i in results) {
        var doc = results[i];
        doc.userUsername = username;

        collection.upsertDocument(
          collection.getSelfLink(),
          doc);
      }
    });
}

このストアド プロシージャは、ユーザーの ID と新しいユーザー名をパラメーターとして受け取り、次のことを行います。

  • userId と一致するすべての項目をフェッチします (投稿、コメント、いいね!)
  • これらの項目ごとに次のことを行います
    • userUsername を置き換えます
    • 項目を置き換えます

重要

このストアド プロシージャを posts コンテナーのすべてのパーティションで実行する必要があるため、この操作にはコストがかかります。 ほとんどのユーザーはサインアップ時に適切なユーザー名を選択し、変更することはないものと想定されるので、この更新が実行されることはほとんどありません。

V2 でのパフォーマンス向上の内容

V2 のパフォーマンス向上について説明しましょう。

[Q2] 投稿を取得する

これで、非正規化が行われたので、その要求を処理するために必要なのは 1 つの項目をフェッチすることだけです。

非正規化された投稿コンテナーから 1 つの項目を取得する図。

待機時間 RU 料金 パフォーマンス
2 ms 1 RU

[Q4] 投稿のコメントを一覧表示する

ここでも、ユーザー名をフェッチしていた余分な要求を行わなくてよくなり、パーティション キーでフィルター処理を行う 1 つのクエリだけになります。

非正規化された投稿のすべてのコメントを取得する図。

待機時間 RU 料金 パフォーマンス
4 ms 7.72 RU

[Q5] 投稿のいいね! を一覧表示する

いいね! を一覧表示するときもまったく同じ状況です。

非正規化された投稿のすべてのいいね! を取得する図。

待機時間 RU 料金 パフォーマンス
4 ms 8.92 RU

V3: すべての要求がスケーラブルであることを確認する

全体的なパフォーマンスの向上を調べると、完全には最適化されていない要求がまだ 2 つあります。 これらの要求は [Q3][Q6] です。 これらは、対象コンテナーのパーティション キーでフィルター処理を行っていないクエリに関連する要求です。

[Q3] ユーザーの投稿を短い形式で一覧表示する

この要求は既に、これ以上のクエリを不要にする V2 で導入された改善によるメリットがあります。

ユーザーの非正規化された投稿を短い形式で一覧表示するクエリを示す図。

しかし、残りのクエリではまだ、posts コンテナーのパーティション キーによるフィルター処理が行われていません。

このような状況について考慮する方法は、単純です。

  1. この要求では、特定のユーザーに対するすべての投稿をフェッチしたいので、userId でフィルター処理を行う "必要があります"。
  2. それは、userId でパーティション分割していない posts コンテナーに対して実行されるため、パフォーマンスがよくありません。
  3. 当然、このパフォーマンスの問題を解決するには、userId でパーティション分割されたコンテナーに対して要求を実行します。
  4. そのようなコンテナーは既にあります。users コンテナーです。

そこで、users コンテナーに投稿全体を複製することにより、2 番目のレベルの非正規化を導入します。 それを行うことで、パーティション分割されているディメンションが異なるだけの、投稿のコピーを実質的に取得し、その userId によっていっそう効率的に取得できるようになります。

users コンテナーには 2 種類の項目が含まれるようになっています。

{
    "id": "<user-id>",
    "type": "user",
    "userId": "<user-id>",
    "username": "<username>"
}

{
    "id": "<post-id>",
    "type": "post",
    "postId": "<post-id>",
    "userId": "<post-author-id>",
    "userUsername": "<post-author-username>",
    "title": "<post-title>",
    "content": "<post-content>",
    "commentCount": <count-of-comments>,
    "likeCount": <count-of-likes>,
    "creationDate": "<post-creation-date>"
}

次の点に注意してください。

  • ユーザーと投稿を区別するため、ユーザー項目に type フィールドが導入されています。
  • また、ユーザー項目に userId フィールドが追加されています。これは id フィールドと重複しますが、users コンテナーが (以前のような id ではなく) userId によってパーティション分割されるようになったため必要です

その非正規化を実現するには、再び変更フィードを使用します。 今回は、posts コンテナーの変更フィードに対応して、新しい投稿または更新された投稿を users コンテナーにディスパッチします。 また、投稿の一覧表示では完全なコンテンツを取得する必要はないので、処理で切り詰めることができます。

ユーザーのコンテナーに投稿を非正規化する図。

これで、クエリを users コンテナーにルーティングし、コンテナーのパーティション キーでフィルター処理できるようになりました。

非正規化されたユーザーのすべての投稿を取得する図。

待機時間 RU 料金 パフォーマンス
4 ms 6.46 RU

[Q6] 最近作成された x 件の投稿を短い形式で一覧表示する (フィード)

ここでも似た状況に対応する必要があります。V2 で導入された非正規化により、残っていた不要なその他のクエリが除去された後も、残りのクエリではコンテナーのパーティション キーによるフィルター処理が行われていません。

最近作成された x 件の投稿を短い形式で一覧表示するクエリを示す図。

同じ方法で、この要求のパフォーマンスとスケーラビリティを最大限にするには、1 つのパーティションのみにヒットすることが必要です。 限られた数の項目だけを返す必要があるので、1 つのパーティションにのみヒットすると考えられます。 このブログ作成プラットフォームのホーム ページを設定するには、最新の 100 件の投稿を取得するだけでよく、データ セット全体をページ分割する必要はありません。

そのため、この最後の要求を最適化するには、この要求を処理するためだけに、3 番目のコンテナーを設計に導入します。 その新しい feed コンテナーに対して投稿を非正規化します。

{
    "id": "<post-id>",
    "type": "post",
    "postId": "<post-id>",
    "userId": "<post-author-id>",
    "userUsername": "<post-author-username>",
    "title": "<post-title>",
    "content": "<post-content>",
    "commentCount": <count-of-comments>,
    "likeCount": <count-of-likes>,
    "creationDate": "<post-creation-date>"
}

type フィールドはこのコンテナーをパーティション分割します。これは常に項目内で post になります。 そうすることで、このコンテナー内のすべての項目は、必ず同じパーティション内に配置されます。

非正規化を実現するには、その新しいコンテナーに投稿をディスパッチするために前に導入した変更フィード パイプラインでフックすることだけが必要です。 考慮する必要がある 1 つの重要な点は、最新の 100 件の投稿のみが格納されることを確認する必要があることです。そうでない場合は、コンテナーのコンテンツがパーティションの最大サイズを超えて拡大する可能性があります。 この制限は、ドキュメントがコンテナーに追加されるたびに事後トリガーを呼び出すことで実装できます。

投稿を feed コンテナーに非正規化する図。

コレクションを切り詰める事後トリガーの本体を次に示します。

function truncateFeed() {
  const maxDocs = 100;
  var context = getContext();
  var collection = context.getCollection();

  collection.queryDocuments(
    collection.getSelfLink(),
    "SELECT VALUE COUNT(1) FROM f",
    function (err, results) {
      if (err) throw err;

      processCountResults(results);
    });

  function processCountResults(results) {
    // + 1 because the query didn't count the newly inserted doc
    if ((results[0] + 1) > maxDocs) {
      var docsToRemove = results[0] + 1 - maxDocs;
      collection.queryDocuments(
        collection.getSelfLink(),
        `SELECT TOP ${docsToRemove} * FROM f ORDER BY f.creationDate`,
        function (err, results) {
          if (err) throw err;

          processDocsToRemove(results, 0);
        });
    }
  }

  function processDocsToRemove(results, index) {
    var doc = results[index];
    if (doc) {
      collection.deleteDocument(
        doc._self,
        function (err) {
          if (err) throw err;

          processDocsToRemove(results, index + 1);
        });
    }
  }
}

最後のステップは、新しい feed コンテナーにクエリを再ルーティングすることです。

最新の投稿を取得する図。

待機時間 RU 料金 パフォーマンス
9 ms 16.97 RU

まとめ

設計のさまざまなバージョンに導入したパフォーマンスとスケーラビリティの全体的な向上を調べてみましょう。

V1 V2 V3
[C1] 7 ms / 5.71 RU 7 ms / 5.71 RU 7 ms / 5.71 RU
[Q1] 2 ms / 1 RU 2 ms / 1 RU 2 ms / 1 RU
[C2] 9 ms / 8.76 RU 9 ms / 8.76 RU 9 ms / 8.76 RU
[Q2] 9 ms / 19.54 RU 2 ms / 1 RU 2 ms / 1 RU
[Q3] 130 ms / 619.41 RU 28 ms / 201.54 RU 4 ms / 6.46 RU
[C3] 7 ms / 8.57 RU 7 ms / 15.27 RU 7 ms / 15.27 RU
[Q4] 23 ms / 27.72 RU 4 ms / 7.72 RU 4 ms / 7.72 RU
[C4] 6 ms / 7.05 RU 7 ms / 14.67 RU 7 ms / 14.67 RU
[Q5] 59 ms / 58.92 RU 4 ms / 8.92 RU 4 ms / 8.92 RU
[Q6] 306 ms / 2063.54 RU 83 ms / 532.33 RU 9 ms / 16.97 RU

読み取り負荷の高いシナリオを最適化しました

お気付きかもしれませんが、ここでの作業では、書き込み要求 (コマンド) を犠牲にして、読み取り要求 (クエリ) のパフォーマンスを向上させることに集中しています。 多くの場合、書き込み操作では、変更フィードによって後続の非正規化がトリガーされるようになったため、計算の負荷は大きくなり、実現には時間がかかります。

これが読み取りパフォーマンスに重点を置いていることは、(多くのソーシャル アプリと同様に) ブログ作成プラットフォームでは読み取り負荷が高いという事実によって正当化されます。 読み取り負荷の高いワークロードの場合、通常、対応する必要がある読み取り要求の量が、書き込み要求数より桁違いに多いことを示します。 そのため、読み取り要求を低コストで高パフォーマンスにするため、書き込み要求の実行コストを高くすることには意味があります。

ここで行ったものの中で最も極端な最適化を調べた場合、[Q6] は 2000 以上の RU がわずか 17 RU になりました。項目あたり約 10 RU のコストで投稿を非正規化することによって、それを実現しました。 投稿の作成または更新より多くのフィード要求に対応するので、全体的なコスト削減を考えると、この非正規化のコストはごくわずかです。

非正規化は段階的に適用できる

この記事で調べたスケーラビリティの向上には、データ セット全体のデータの非正規化と複製が含まれます。 これらの最適化を一度に行う必要はないことに注意する必要があります。 パーティション キーでフィルター処理を行うクエリは大きな規模でパフォーマンスが向上しますが、あまり呼び出されない場合、または限られたデータ セットに対して呼び出される場合であれば、クロスパーティション クエリでも問題ありません。 プロトタイプを構築するだけの場合、または小規模の制御されたユーザー ベースで製品を使い始める場合は、おそらく、これらの改善を後回しにすることができます。 その場合に重要なことは、モデルのパフォーマンスを監視して、いつ着手すべきかを判断できるようにすることです。

更新を他のコンテナーに配布するために使用した変更フィードでは、すべての更新が永続的に格納されます。 この永続性により、コンテナーの作成時からのすべての更新を要求することができ、システムに既に多数のデータが存在する場合であっても、1 回限りのキャッチアップ操作として非正規化されたビューを開始できます。

次のステップ

実用的なデータ モデリングとパーティション分割に関するこの概要の後は、ここで説明した概念を次の記事で確認できます。