MongoDBの適切なデータモデリング:埋め込みドキュメント vs. 参照ドキュメント
ドキュメントデータベースとしてのMongoDBの柔軟性により、開発者はデータの関係性をいくつかの方法でモデル化できます。厳密に正規化されたスキーマを強制する従来のRDBとは異なり、MongoDBはコレクション内の関連データを構造化するための2つの主要かつ強力な戦略を提供します:埋め込み(Embedding)と参照(Referencing)です。正しいアプローチを選択することは、アプリケーションのパフォーマンス、データの整合性、クエリの複雑さ、スケーラビリティに直接影響するため、非常に重要です。
このガイドでは、親ドキュメント内にドキュメントを埋め込むことと、異なるコレクション間で関連ドキュメントを参照することのトレードオフを深く掘り下げます。これらのテクニックをいつ、どのように適用するかを理解することで、アプリケーション固有のアクセスパターンに合わせて、効率的で高性能なMongoDBスキーマを設計できるようになります。
MongoDBのデータモデリング戦略の理解
MongoDBは、コレクションに格納されるドキュメント(JSONオブジェクトに似ています)にデータを整理します。これらのドキュメント間の関係は、2つのコアパターンを使用してモデル化できます。
- 埋め込み(非正規化): 関連データを親ドキュメントの直接内部に格納します。
- 参照(正規化): 別のコレクション内の関連ドキュメントへの参照(
_idのようなもの)のみを格納します。これは外部キーに似ています。
1. 埋め込みパターン(非正規化)
埋め込みとは、あるドキュメントを別のドキュメントの直接内部に配置することです。このテクニックは、データ関係が「1対少数」(one-to-few)である場合や、関連データが親ドキュメントと一緒に頻繁にアクセスされる場合に、MongoDBで強く推奨されます。
埋め込みを使用するタイミング
以下の場合に埋め込みパターンを使用してください。
- データが一緒にアクセスされる場合: 親をクエリする際に、ほぼ常にその関連データも必要とする場合、埋め込みは完全な情報セットを取得するために必要なデータベース操作の回数を最小限に抑えます。
- 1対少数の関係: 埋め込まれたドキュメントの配列が比較的少なく予測可能な関係に理想的です(例:ユーザーの最後の10回のログインアクティビティ、または注文の明細項目)。
- データの整合性が重要である場合: 埋め込まれたデータは、単一のドキュメント内に存在するため、本質的に整合性が取れています。これにより、MongoDBの単一ドキュメントACIDトランザクションによって提供されるアトミック性の保証が簡素化されます。
埋め込みの例
ProductとそのReviewsを考えてみましょう。レビューが製品と一緒に頻繁に取得され、レビューの総数が管理可能な場合:
// Product Collection Document
{
"_id": ObjectId("..."),
"name": "High-Performance SSD",
"price": 129.99,
"reviews": [
{
"user": "Alice",
"rating": 5,
"comment": "Fastest drive ever!"
},
{
"user": "Bob",
"rating": 4,
"comment": "Great value."
}
]
}
埋め込みの欠点
- ドキュメントサイズ制限: MongoDBのドキュメントには最大16MBのサイズ制限があります。埋め込まれたドキュメントの配列が無制限に成長すると、最終的にこの制限に達し、参照への移行が必要になります。
- 更新のオーバーヘッド: 単一の埋め込み要素を更新するには、親ドキュメント全体を書き直す必要があります。親ドキュメントが非常に大きい場合、これは非効率的になる可能性があります。
- データの重複: 埋め込まれたデータが親とは独立して共有または表示される必要がある場合、更新がすべてのコピーで同期されないと、データの重複と最終的な整合性の問題が発生するリスクがあります。
2. 参照パターン(正規化)
参照は、RDBにおける外部キーの概念を模倣します。関連データを埋め込む代わりに、関連ドキュメントの_id(またはIDの組み合わせ)を親ドキュメントに格納します。これには、実際の関連データを取得するために2番目のクエリ($lookup集計ステージまたはアプリケーション側での結合)が必要です。
参照を使用するタイミング
以下の場合に参照パターンを使用してください。
- 1対多または多対多の関係: 関係の一方の側が無制限に成長する可能性がある場合(例:ブログ記事のコメント数、または多くのグループに所属するユーザー数)。
- 複数の親にわたって共有されるデータ: 関連データエンティティが独立して更新され、他の複数のドキュメントによってアクセスされる必要がある場合(例:多くの
Productドキュメントによって使用されるCategoryドキュメント)。 - 大規模なデータセット: 埋め込みが16MBのドキュメントサイズ制限に違反する場合。
参照の種類
A. 手動参照(アプリケーション側での結合)
親ドキュメントに_idを格納します。
// Author Collection
{
"_id": ObjectId("author123"),
"name": "Jane Doe"
}
// Book Collection
{
"_id": ObjectId("book456"),
"title": "Data Modeling 101",
"author_id": ObjectId("author123") // Reference
}
著者の名前を取得するには、2つのクエリを実行するか、$lookupを使用します。
// 集計フレームワークでの$lookupの使用例
db.books.aggregate([
{ $match: { title: "Data Modeling 101" } },
{
$lookup: {
from: "authors", // 結合するコレクション
localField: "author_id", // 入力ドキュメント(books)のフィールド
foreignField: "_id", // 'from'コレクション(authors)のドキュメントのフィールド
as: "author_details"
}
}
]);
B. 双方向参照
双方向の関係では、子ドキュメントで親を参照することもできます。これにより、両方向の関係をたどりやすくなりますが、2か所で更新が発生するため、書き込みオーバーヘッドが増加します。
参照の欠点
- クエリの複雑さの増加: 完全に非正規化されたデータを取得するには、結合(アプリケーションコードまたはMongoDBの
$lookup経由)が必要であり、単一の埋め込み読み取り操作よりも遅くなる可能性があります。 - 整合性の管理: 参照されているデータ(例:著者の名前変更)を変更した場合、その著者を参照しているすべてのドキュメントを手動で更新する必要があります。さもないと、更新されるまで一部のドキュメントが古いデータを表示することになります。
まとめ:正しい選択をする
埋め込みと参照の間の決定は、アクセスパターンを中心に展開されます。自問自答してください:「この関連データはどのくらいの頻度で取得されますか?どのくらいの頻度で変更されますか?それは小さいですか、それとも巨大になる可能性がありますか?」
| 特徴 / 考慮事項 | 埋め込み(非正規化) | 参照(正規化) |
|---|---|---|
| 読み取りパフォーマンス | 優秀(単一クエリ) | 良好~普通(結合が必要) |
| 書き込みパフォーマンス | 不十分(ドキュメント全体の書き直し) | 良好(参照ポイントの更新のみ) |
| データサイズ制限 | 16MBに制限 | 実質的な制限なし |
| 関係のタイプ | 1対少数 | 1対多、多対多 |
| データの整合性 | 高(アトミック書き込み) | 手動管理(古いデータの可能性あり) |
ベストプラクティス:埋め込みから始め、後で移行する
一般的で効果的な戦略は、一緒に頻繁に読み取ることがわかっているデータは、まず埋め込むことです。これにより、一般的なケースが最適化されます。後で、ドキュメントサイズの増大や過剰な更新の複雑さによるパフォーマンスのボトルネックに遭遇した場合、その特定のデータ部分を独自のコレクションに移行し、参照に切り替えることができます。
結論
MongoDBは、アプリケーションのニーズに応じて読み取りまたは書き込みを最適化する柔軟性を提供します。埋め込みは、データが緊密に結合されている場合に、迅速な読み取りアクセスと引き換えに更新の単純さを犠牲にします。参照は、より複雑な結合を伴う読み取り操作のコストで、データ整合性を維持し、無制限の成長を処理します。アプリケーションの読み書き比率と関係のカーディナリティを慎重に分析することで、パフォーマンスと保守性を最大化するMongoDBスキーマを設計できます。