適切なMongoDBデータモデルの選択:組み込みドキュメントと参照ドキュメントの比較

アクセスパターン、成長、一貫性、更新動作に基づいて、埋め込みドキュメントと参照ドキュメントのどちらを選択するかを決定します。

適切なMongoDBデータモデルの選択:埋め込みドキュメントと参照ドキュメント

MongoDBのデータモデリングは、通常、1つの実用的な質問に帰着します。関連データを同じドキュメント内に配置すべきか、それとも別のドキュメントを参照すべきか?この選択は、読み取り速度、更新コスト、ドキュメントの成長、そしてアプリケーションが処理しなければならない一貫性の作業量に影響を与えます。

このガイドでは、親ドキュメント内にドキュメントを埋め込むことと、異なるコレクション間で関連ドキュメントを参照することのトレードオフについて深く掘り下げます。これらのテクニックをいつ、どのように適用するかを理解することで、アプリケーションの特定のアクセスパターンに合わせた、効率的で高性能なMongoDBスキーマを設計できるようになります。

MongoDBデータモデリング戦略の理解

MongoDBは、データをコレクションに格納されたドキュメント(JSONオブジェクトに類似)に整理します。これらのドキュメント間の関係は、2つのコアパターンを使用してモデル化できます。

  1. 埋め込み(非正規化): 関連データを親ドキュメント内に直接格納します。
  2. 参照(正規化): 外部キーと同様に、別のコレクション内の関連ドキュメントへの参照(_idなど)のみを格納します。

1. 埋め込みパターン(非正規化)

埋め込みでは、あるドキュメントを別のドキュメント内に直接配置します。このテクニックは、データ関係が1対少数である場合、または関連データが親ドキュメントと一緒に頻繁にアクセスされる場合に、MongoDBで非常に推奨されます。

埋め込みを使用する場合

次の場合に埋め込みパターンを使用します。

  • データが一緒にアクセスされる: 親をクエリするときに関連データがほぼ常に必要な場合、埋め込みにより、完全な情報セットを取得するために必要なデータベース操作の数が最小限に抑えられます。
  • 1対少数の関係: 埋め込まれたドキュメントの配列が比較的小さく予測可能なままである関係に最適です(例:ユーザーの最後の10回のログインアクティビティ、注文の明細項目)。
  • データの一貫性が重要: 埋め込まれたデータは単一のドキュメント内に存在するため、本質的に一貫性があり、MongoDBの単一ドキュメントACIDトランザクションによって提供される原子性の保証が簡素化されます。

埋め込みの例

ProductとそのReviewsを考えてみます。レビューが製品と一緒に頻繁に取得され、レビューの総数が管理可能な場合:

// 製品コレクションドキュメント
{
  "_id": ObjectId("..."),
  "name": "高性能SSD",
  "price": 129.99,
  "reviews": [
    {
      "user": "Alice",
      "rating": 5,
      "comment": "最速のドライブです!"
    },
    {
      "user": "Bob",
      "rating": 4,
      "comment": "素晴らしい価値です。"
    }
  ]
}

埋め込みの欠点

  1. ドキュメントサイズの制限: MongoDBドキュメントには最大16MBのサイズ制限があります。埋め込まれたドキュメントの配列が明確な境界なしに成長する場合、最終的には参照またはバケット化が必要になる可能性があります。
  2. 更新のオーバーヘッド: 大きな埋め込み配列は、更新をより高価にし、親ドキュメントの競合を増加させる可能性があります。
  3. データの重複: 埋め込まれたデータを親から独立して共有または表示する必要がある場合、データの重複と、更新がすべてのコピー間で同期されない場合の結果的な一貫性の問題が発生するリスクがあります。

2. 参照パターン(正規化)

参照は、リレーショナルデータベースの外部キーの概念を模倣します。関連データを埋め込む代わりに、関連ドキュメントの_idまたは別の安定した識別子を格納します。これには通常、関連データを取得するために、2番目のクエリ、$lookup集約ステージ、またはアプリケーション側の結合が必要です。

参照を使用する場合

次の場合に参照パターンを使用します。

  • 1対多または多対多の関係: 関係の一方が無制限に成長する可能性がある場合(例:ブログ投稿へのコメント数、多くのグループに属するユーザー)。
  • 複数の親間で共有されるデータ: 関連データエンティティを独立して更新し、他の複数のドキュメントからアクセスする必要がある場合(例:多くのProductドキュメントで使用されるCategoryドキュメント)。
  • 大規模なデータセット: 埋め込みが16MBのドキュメントサイズ制限に違反する場合。

参照の種類

A. 手動参照(アプリケーション側の結合)

親ドキュメントに_idを格納します:

// 著者コレクション
{
  "_id": ObjectId("author123"),
  "name": "Jane Doe"
}

// 書籍コレクション
{
  "_id": ObjectId("book456"),
  "title": "データモデリング101",
  "author_id": ObjectId("author123") // 参照
}

著者の名前を取得するには、2つのクエリを実行するか、$lookupを使用します:

// 集約フレームワークでの$lookupの使用例
db.books.aggregate([
  { $match: { title: "データモデリング101" } },
  {
    $lookup: {
      from: "authors",          // 結合するコレクション
      localField: "author_id",  // 入力ドキュメント(books)からのフィールド
      foreignField: "_id",      // 'from'コレクション(authors)のドキュメントからのフィールド
      as: "author_details"
    }
  }
]);

B. 双方向参照

双方向の関係の場合、子ドキュメントにも親を参照させることができます。これにより、両方向での関係のトラバースが容易になりますが、更新を2か所で行う必要があるため、書き込みオーバーヘッドが増加します。

参照の欠点

  1. クエリの複雑さの増加: 完全に非正規化されたデータを取得するには、結合(アプリケーションコードまたはMongoDBの$lookupを介して)が必要であり、単一の埋め込み読み取り操作よりも遅くなる可能性があります。
  2. 一貫性管理: 著者の表示名など、参照ドキュメントから選択したフィールドを複製する場合、それらのコピーを更新するか、一時的な古さを受け入れる必要があります。

まとめ:適切な選択をする

埋め込みと参照のどちらを選択するかは、アクセスパターンに基づいています。自問してみてください:この関連データはどのくらいの頻度で取得されるか?どのくらいの頻度で変更されるか?小さいか、それとも潜在的に巨大か?

機能/考慮事項 埋め込み(非正規化) 参照(正規化)
読み取りパフォーマンス 優れている(単一クエリ) 良好から普通(結合が必要)
書き込みパフォーマンス 大規模またはホットな親ドキュメントではコストがかかる可能性がある 独立したドキュメントでは多くの場合よりシンプル
データサイズ制限 16MBのドキュメント制限に制限される 1つの巨大な親ドキュメントを回避するが、インデックスとクエリ制限を慎重に設計する必要がある
関係タイプ 1対少数 1対多、多対多
データの一貫性 高い(アトミックな書き込み) 手動で管理(潜在的な古さ)

ベストプラクティスのヒント:埋め込みから始め、後で切り替える

一般的で効果的な戦略は、頻繁に一緒に読み取るとわかっているデータを埋め込むことから始めることです。これにより、一般的なケースに最適化されます。後で、ドキュメントの大規模な成長や過度の更新の複雑さによるパフォーマンスのボトルネックに遭遇した場合、その特定のデータを独自のコレクションに切り替え、参照に変更できます。

要点

MongoDBスキーマは、実際のクエリに一致する場合に最も効果的に機能します。一緒に読み取り、境界を維持できるデータを埋め込みます。独立して成長するデータ、多くの親によって共有されるデータ、または独自のスケジュールで変更されるデータを参照します。モデルを決定する前に、主要な読み取りと書き込みを書き留め、現実的なドキュメントサイズでそれらのパスをテストしてください。