序章

リレーショナルデータベースの使用経験が豊富な場合、テーブルやリレーションシップの観点から考えるなど、リレーショナルモデルの原則を超えることは難しい場合があります。 MongoDB のようなドキュメント指向データベースを使用すると、リレーショナルモデルの硬直性や制限から解放されます。 ただし、自己記述型ドキュメントをデータベースに保存できることに伴う柔軟性と自由度は、他の落とし穴や困難につながる可能性があります。

この概念的な記事では、ドキュメント指向データベースのスキーマ設計に関連する5つの一般的なガイドラインの概要を説明し、データ間の関係をモデル化するときに行う必要のあるさまざまな考慮事項に焦点を当てます。 また、配列内にドキュメントを埋め込んだり、子と親の参照を使用したりするなど、このような関係をモデル化するために採用できるいくつかの戦略と、これらの戦略を使用するのが最も適切な場合についても説明します。

ガイドライン1—一緒にアクセスする必要があるものを一緒に保存する

一般的なリレーショナルデータベースでは、データはテーブルに保持され、各テーブルは、エンティティ、オブジェクト、またはイベントを構成するさまざまな属性を表す列の固定リストで構成されます。 たとえば、大学の学生を表すテーブルに、各学生の名、姓、生年月日、および一意の識別番号を保持する列が表示される場合があります。

通常、各テーブルは単一のサブジェクトを表します。 学生の現在の研究、奨学金、または以前の教育に関する情報を保存したい場合は、そのデータを個人情報を保持しているテーブルとは別のテーブルに保持するのが理にかなっています。 次に、これらのテーブルを接続して、各テーブルのデータ間に関係があることを示し、テーブルに含まれる情報に意味のある接続があることを示します。

たとえば、各学生の奨学金ステータスを説明する表では、学生ID番号で学生を参照できますが、データの重複を避けるために、学生の名前や住所を直接保存することはできません。 このような場合、学生のソーシャルメディアアカウント、事前教育、および奨学金に関するすべての情報を含む学生に関する情報を取得するには、クエリで一度に複数のテーブルにアクセスし、さまざまなテーブルの結果を1つにまとめる必要があります。 。

参照を通じて関係を記述するこの方法は、正規化データモデルとして知られています。 この方法でデータを保存する(相互に関連する複数の個別の簡潔なオブジェクトを使用する)ことは、ドキュメント指向データベースでも可能です。 ただし、ドキュメントモデルの柔軟性と、埋め込まれたドキュメントと配列を1つのドキュメント内に格納する自由度により、リレーショナルデータベースとは異なる方法でデータをモデル化できます。

ドキュメント指向データベースでデータをモデル化するための基本的な概念は、「一緒にアクセスされるものを一緒に保存する」ことです。学生の例をさらに掘り下げて、この学校のほとんどの学生は複数の電子メールアドレスを持っていると言います。 このため、大学は、各学生の連絡先情報とともに複数の電子メールアドレスを保存する機能を望んでいます。

このような場合、サンプルドキュメントは次のような構造になります。

{
    "_id": ObjectId("612d1e835ebee16872a109a4"),
    "first_name": "Sammy",
    "last_name": "Shark",
    "emails": [
        {
            "email": "[email protected]",
            "type": "work"
        },
        {
            "email": "[email protected]",
            "type": "home"
        }
    ]
}

このサンプルドキュメントには、電子メールアドレスの埋め込みリストが含まれていることに注意してください。

1つのドキュメント内で複数の主題を表すことは、非正規化データモデルを特徴づけます。 これにより、アプリケーションは、複数の個別のオブジェクトやコレクションにアクセスすることなく、特定のオブジェクト(ここでは学生)に関連するすべてのデータを一度に取得して操作できます。 そうすることで、整合性を保証するためにマルチドキュメントトランザクションを使用することなく、そのようなドキュメントに対する操作の原子性も保証されます。

埋め込まれたドキュメントを使用して一緒にアクセスする必要があるものを一緒に保存することは、多くの場合、ドキュメント指向データベースでデータを表すための最適な方法です。 次のガイドラインでは、1対1または1対多の関係など、オブジェクト間のさまざまな関係をドキュメント指向データベースで最適にモデル化する方法を学習します。

ガイドライン2—埋め込みドキュメントとの1対1の関係のモデリング

1対1の関係は、1つのオブジェクトが別の種類のオブジェクトと正確に接続されている2つの異なるオブジェクト間の関連付けを表します。

前のセクションの学生の例を続けると、各学生は、任意の時点で有効な学生IDカードを1枚だけ持っています。 1枚のカードが複数の学生に属することはありません。また、学生が複数のIDカードを持つことはできません。 このすべてのデータをリレーショナルデータベースに保存する場合は、学生の記録とIDカードの記録を参照によって結び付けられた別々のテーブルに保存することで、学生とIDカードの関係をモデル化するのが理にかなっています。

ドキュメントデータベースでこのような関係を表す一般的な方法の1つは、埋め込みドキュメントを使用することです。 例として、次のドキュメントでは、Sammyという名前の学生とその学生IDカードについて説明しています。

{
    "_id": ObjectId("612d1e835ebee16872a109a4"),
    "first_name": "Sammy",
    "last_name": "Shark",
    "id_card": {
        "number": "123-1234-123",
        "issued_on": ISODate("2020-01-23"),
        "expires_on": ISODate("2020-01-23")
    }
}

このサンプルドキュメントは、単一の値ではなく、 id_card フィールドには、ID番号、カードの発行日、およびカードの有効期限で記述された、学生のIDカードを表す埋め込みドキュメントが保持されます。 身分証明書は、実際には別のオブジェクトですが、基本的に学生のサミーを説明するドキュメントの一部になります。 通常、単一のクエリですべての関連情報を取得できるように、このようにドキュメントスキーマを構成することは適切な選択です。

ある種類のオブジェクトと、学生の電子メールアドレス、受講するコース、学生評議会の掲示板に投稿するメッセージなど、ある種類のオブジェクトを別の種類の多くのオブジェクトと結び付ける関係に遭遇すると、事態は簡単ではなくなります。 次のいくつかのガイドラインでは、これらのデータの例を使用して、1対多および多対多の関係を操作するためのさまざまなアプローチを学習します。

ガイドライン3—埋め込みドキュメントとの1対数の関係のモデリング

あるタイプのオブジェクトが別のタイプの複数のオブジェクトに関連している場合、それは1対多の関係として説明できます。 学生は複数の電子メールアドレスを持つことができ、車は多数の部品を持つことができ、または買い物注文は複数のアイテムで構成されることができます。 これらの例はそれぞれ、1対多の関係を表しています。

ドキュメントデータベースで1対1の関係を表す最も一般的な方法は、埋め込みドキュメントを使用する方法ですが、ドキュメントスキーマで1対多の関係をモデル化する方法はいくつかあります。 ただし、これらを最適にモデル化する方法のオプションを検討する場合は、特定の関係に3つのプロパティを検討する必要があります。

  • カーディナリティカーディナリティは、特定のセット内の個々の要素の数の尺度です。 たとえば、クラスに30人の生徒がいる場合、そのクラスのカーディナリティは30であると言えます。 1対多の関係では、カーディナリティはそれぞれの場合で異なる可能性があります。 学生は1つまたは複数の電子メールアドレスを持つことができます。 ほんの数クラスに登録することも、完全にスケジュールを組むこともできます。 1対多の関係では、「多」のサイズがデータのモデル化方法に影響します。
  • 独立したアクセス:一部の関連データは、メインオブジェクトとは別にアクセスされることはめったにありません。 たとえば、他の学生の詳細なしで1人の学生の電子メールアドレスを取得することはまれな場合があります。 一方、大学のコースは、それらに参加するために登録されている学生に関係なく、個別にアクセスして更新する必要がある場合があります。 関連するドキュメントに単独でアクセスするかどうかも、データのモデル化方法に影響します。
  • データ間の関係が厳密に1対多の関係であるかどうか:例の学生が大学で通っているコースを考えてみましょう。 学生の観点から、彼らは複数のコースに参加することができます。 一見すると、これは1対多の関係のように見えるかもしれません。 ただし、大学のコースに1人の学生が参加することはめったにありません。 多くの場合、複数の学生が同じクラスに参加します。 このような場合、問題の関係は実際には1対多の関係ではなく、多対多の関係であるため、この関係をモデル化するには、1対多の関係とは異なるアプローチを取ります。多くの関係。

学生の電子メールアドレスを保存する方法を決定していると想像してください。 各学生は、仕事用、個人用、大学から提供されたものなど、複数の電子メールアドレスを持つことができます。 単一の電子メールアドレスを表すドキュメントは、次のような形式をとることがあります。

{
    "email": "[email protected]",
    "type": "work"
}

カーディナリティに関しては、学生が数十、ましてや数百の電子メールアドレスを持っている可能性は低いため、各学生の電子メールアドレスはわずかです。 したがって、この関係は1対数の関係として特徴付けることができます。これは、電子メールアドレスを学生のドキュメントに直接埋め込んで一緒に保存する説得力のある理由です。 電子メールアドレスのリストが無期限に増えて、ドキュメントが大きくなり、使用が非効率になるリスクはありません。

:データを配列に格納することに関連する特定の落とし穴があることに注意してください。 たとえば、単一のMongoDBドキュメントのサイズは16MBを超えることはできません。 配列フィールドを使用して複数のドキュメントを埋め込むことは可能であり、一般的ですが、オブジェクトのリストが制御不能に大きくなると、ドキュメントはすぐにこのサイズ制限に達する可能性があります。 さらに、埋め込み配列内に大量のデータを格納すると、クエリのパフォーマンスに大きな影響があります。

配列フィールドに複数のドキュメントを埋め込むことは、多くの状況で適切である可能性がありますが、それが常に最良の解決策であるとは限らないことも知っておいてください。

独立したアクセスに関しては、電子メールアドレスは学生とは別にアクセスされない可能性があります。 そのため、それらを別々のドキュメントとして別々のコレクションに保存する明確な動機はありません。 これは、学生のドキュメント内にそれらを埋め込むもう1つの説得力のある理由です。

最後に考慮すべきことは、この関係が多対多の関係ではなく、実際には1対多の関係であるかどうかです。 電子メールアドレスは1人の人物に属しているため、この関係を多対多の関係ではなく、1対多の関係(または、より正確には1対少数の関係)として説明するのが妥当です。

これらの3つの仮定は、学生自身を説明する同じドキュメント内に学生のさまざまな電子メールアドレスを埋め込むことが、この種のデータを保存するための適切な選択であることを示唆しています。 電子メールアドレスが埋め込まれたサンプルの学生のドキュメントは、次のような形になる可能性があります。

{
    "_id": ObjectId("612d1e835ebee16872a109a4"),
    "first_name": "Sammy",
    "last_name": "Shark",
    "emails": [
        {
            "email": "[email protected]",
            "type": "work"
        },
        {
            "email": "[email protected]",
            "type": "home"
        }
    ]
}

この構造を使用すると、学生のドキュメントを取得するたびに、同じ読み取り操作で埋め込まれた電子メールアドレスも取得します。

関連するドキュメントに個別にアクセスする必要がない、1対数の種類の関係をモデル化する場合、スキーマの複雑さを軽減できるため、通常、このようにドキュメントを直接埋め込むことが望ましいです。

ただし、前述のように、このようなドキュメントを埋め込むことが常に最適なソリューションであるとは限りません。 次のセクションでは、一部のシナリオでこれが当てはまる理由について詳しく説明し、ドキュメントデータベースで関係を表すための代替方法として子参照を使用する方法の概要を説明します。

ガイドライン4—子参照との1対多および多対多の関係のモデリング

学生とその電子メールアドレスとの関係の性質から、その関係をドキュメントデータベースでモデル化するのに最適な方法がわかりました。 これと、学生と受講するコースの関係にはいくつかの違いがあるため、学生とそのコースの関係をモデル化する方法も異なります。

学生が参加する単一のコースを説明するドキュメントは、次のような構造に従うことができます。

{
    "name": "Physics 101",
    "department": "Department of Physics",
    "points": 7
}

次の例のように、最初から埋め込みドキュメントを使用して各学生のコースに関する情報を保存することにしたとします。

{
    "_id": ObjectId("612d1e835ebee16872a109a4"),
    "first_name": "Sammy",
    "last_name": "Shark",
    "emails": [
        {
            "email": "[email protected]",
            "type": "work"
        },
        {
            "email": "[email protected]",
            "type": "home"
        }
    ],
    "courses": [
        {
            "name": "Physics 101",
            "department": "Department of Physics",
            "points": 7
        },
        {
            "name": "Introduction to Cloud Computing",
            "department": "Department of Computer Science",
            "points": 4
        }
    ]
}

これは完全に有効なMongoDBドキュメントであり、目的を十分に果たすことができますが、前のガイドラインで学習した3つの関係プロパティを考慮してください。

1つ目はカーディナリティです。 学生はおそらく少数の電子メールアドレスしか保持しませんが、学習中に複数のコースに参加できます。 数年の出席の後、学生が参加した数十のコースが存在する可能性があります。 さらに、彼らは、同じように彼らの長年の出席にわたって彼ら自身の一連のコースに出席している他の多くの学生と一緒にこれらのコースに出席するでしょう。

前の例のように各コースを埋め込むことにした場合、学生のドキュメントはすぐに扱いにくくなります。 カーディナリティが高くなると、埋め込みドキュメントのアプローチは説得力がなくなります。

2番目の考慮事項は、独立したアクセスです。 電子メールアドレスとは異なり、大学のコースに関する情報を自分で取得する必要がある場合があると想定するのは妥当です。 たとえば、マーケティングパンフレットを作成するために、利用可能なコースに関する情報が必要だとします。 さらに、コースは時間の経過とともに更新する必要があります。コースを教える教授が変更されたり、スケジュールが変動したり、前提条件が更新されたりする可能性があります。

学生のドキュメントに埋め込まれたドキュメントとしてコースを保存する場合、大学が提供するすべてのコースのリストを取得するのは面倒になります。 また、コースの更新が必要になるたびに、すべての学生の記録を調べて、あらゆる場所でコース情報を更新する必要があります。 どちらも、コースを個別に保存し、完全に埋め込まない理由です。

考慮すべき3番目のことは、学生と大学のコースの関係が実際に1対多なのか、それとも多対多なのかということです。 この場合、各コースに複数の学生が参加できるため、後者になります。 この関係のカーディナリティと独立したアクセスの側面は、主にアクセスと更新の容易さなどの実用的な理由から、各コースドキュメントを埋め込むことを禁じています。 コースと学生の関係の多対多の性質を考慮すると、コースドキュメントを独自の一意の識別子を持つ別のコレクションに保存することが理にかなっている場合があります。

この個別のコレクションのクラスを表すドキュメントは、次の例のような構造を持つ可能性があります。

{
    "_id": ObjectId("61741c9cbc9ec583c836170a"),
    "name": "Physics 101",
    "department": "Department of Physics",
    "points": 7
},
{
    "_id": ObjectId("61741c9cbc9ec583c836170b"),
    "name": "Introduction to Cloud Computing",
    "department": "Department of Computer Science",
    "points": 4
}

このようなコース情報を保存する場合は、どの学生がどのコースに参加しているかがわかるように、学生をこれらのコースに接続する方法を見つける必要があります。 関連するオブジェクトの数があまり多くない場合、特に多対多の関係がある場合、これを行う一般的な方法の1つは、子参照を使用することです。

子参照を使用すると、学生のドキュメントは、次の例のように、学生が参加するコースのオブジェクト識別子を埋め込み配列で参照します。

{
    "_id": ObjectId("612d1e835ebee16872a109a4"),
    "first_name": "Sammy",
    "last_name": "Shark",
    "emails": [
        {
            "email": "[email protected]",
            "type": "work"
        },
        {
            "email": "[email protected]",
            "type": "home"
        }
    ],
    "courses": [
        ObjectId("61741c9cbc9ec583c836170a"),
        ObjectId("61741c9cbc9ec583c836170b")
    ]
}

このサンプルドキュメントにはまだ courses これも配列ですが、前の例のように完全なコースドキュメントを埋め込む代わりに、別のコレクションのコースドキュメントを参照する識別子のみが埋め込まれます。 現在、学生のドキュメントを取得する場合、コースはすぐには利用できないため、個別にクエリする必要があります。 一方、どのコースを取得するかはすぐにわかります。 また、コースの詳細を更新する必要がある場合は、コースドキュメント自体のみを変更する必要があります。 学生とそのコースの間のすべての参照は引き続き有効です。

注:リレーションのカーディナリティが大きすぎて、この方法で子参照を埋め込むことができない場合の確固たる規則はありません。 問題のアプリケーションに最適な場合は、カーディナリティを低くするか高くするかで別のアプローチを選択できます。 結局のところ、アプリケーションがデータを照会および更新する方法に合わせてデータを構造化する必要があります。

関連ドキュメントの量が妥当な範囲内にあり、関連ドキュメントに個別にアクセスする必要がある1対多の関係をモデル化する場合は、関連ドキュメントを個別に保存し、それらに接続するために子参照を埋め込むことをお勧めします。

子参照を使用してさまざまなタイプのデータ間の関係を示す方法を学習したので、このガイドでは、逆の概念である親参照の概要を説明します。

ガイドライン5—親参照との無制限の1対多の関係のモデリング

子参照の使用は、関連するオブジェクトが多すぎて親ドキュメント内に直接埋め込むことができない場合にうまく機能しますが、その量はまだ既知の範囲内です。 ただし、関連するドキュメントの数に制限がなく、時間の経過とともに増加し続ける場合があります。

例として、大学の学生評議会にメッセージボードがあり、コース、旅行の話、求人情報、教材、無料のチャットなど、好きなメッセージを投稿できると想像してみてください。 この例のサンプルメッセージは、件名とメッセージ本文で構成されています。

{
    "_id": ObjectId("61741c9cbc9ec583c836174c"),
    "subject": "Books on kinematics and dynamics",
    "message": "Hello! Could you recommend good introductory books covering the topics of kinematics and dynamics? Thanks!",
    "posted_on": ISODate("2021-07-23T16:03:21Z")
}

前に説明した2つのアプローチ(埋め込みと子の参照)のいずれかを使用して、この関係をモデル化できます。 埋め込みを決定する場合、学生のドキュメントは次のような形になる可能性があります。

{
    "_id": ObjectId("612d1e835ebee16872a109a4"),
    "first_name": "Sammy",
    "last_name": "Shark",
    "emails": [
        {
            "email": "[email protected]",
            "type": "work"
        },
        {
            "email": "[email protected]",
            "type": "home"
        }
    ],
    "courses": [
        ObjectId("61741c9cbc9ec583c836170a"),
        ObjectId("61741c9cbc9ec583c836170b")
    ],
    "message_board_messages": [
        {
            "subject": "Books on kinematics and dynamics",
            "message": "Hello! Could you recommend good introductory books covering the topics of kinematics and dynamics? Thanks!",
            "posted_on": ISODate("2021-07-23T16:03:21Z")
        },
        . . .
    ]
}

ただし、学生がメッセージを書くことに多忙な場合、ドキュメントはすぐに信じられないほど長くなり、16 MBのサイズ制限を簡単に超える可能性があるため、この関係のカーディナリティは埋め込みに反対することを示唆しています。 さらに、メッセージボードページが学生によって投稿された最新のメッセージを表示するように設計されている場合のように、メッセージは学生とは別にアクセスする必要がある場合があります。 これは、埋め込みがこのシナリオにとって最良の選択ではないことも示唆しています。

注:生徒のドキュメントを取得するときに、メッセージボードメッセージに頻繁にアクセスするかどうかも考慮する必要があります。 そうでない場合、メッセージのリストが頻繁に使用されない場合でも、これらすべてをそのドキュメント内に埋め込むと、このドキュメントを取得および操作するときにパフォーマンスが低下します。 関連データへのアクセス頻度が低いことは、ドキュメントを埋め込んではいけないもう1つの手がかりになることがよくあります。

ここで、前の例のように完全なドキュメントを埋め込む代わりに、子参照を使用することを検討してください。 個々のメッセージは別のコレクションに保存され、学生のドキュメントは次の構造を持つことができます。

{
    "_id": ObjectId("612d1e835ebee16872a109a4"),
    "first_name": "Sammy",
    "last_name": "Shark",
    "emails": [
        {
            "email": "[email protected]",
            "type": "work"
        },
        {
            "email": "[email protected]",
            "type": "home"
        }
    ],
    "courses": [
        ObjectId("61741c9cbc9ec583c836170a"),
        ObjectId("61741c9cbc9ec583c836170b")
    ],
    "message_board_messages": [
        ObjectId("61741c9cbc9ec583c836174c"),
        . . .
    ]
}

この例では、 message_board_messages フィールドに、Sammyによって書き込まれたすべてのメッセージへの子参照が格納されるようになりました。 ただし、アプローチを変更すると、メッセージに個別にアクセスできるようになるという点で、前述の問題の1つだけが解決されます。 ただし、子参照アプローチを使用すると、学生のドキュメントサイズはゆっくりと大きくなりますが、この関係の無制限のカーディナリティを考えると、オブジェクト識別子のコレクションも扱いにくくなる可能性があります。 結局のところ、学生は4年間の学習中に何千ものメッセージを簡単に書くことができました。

このようなシナリオでは、あるオブジェクトを別のオブジェクトに接続する一般的な方法は、親参照を使用することです。 前に説明した子の参照とは異なり、これは個々のメッセージを参照する学生のドキュメントではなく、メッセージを書いた学生を指すメッセージのドキュメント内の参照です。

親参照を使用するには、メッセージを作成した学生への参照を含むようにメッセージドキュメントスキーマを変更する必要があります。

{
    "_id": ObjectId("61741c9cbc9ec583c836174c"),
    "subject": "Books on kinematics and dynamics",
    "message": "Hello! Could you recommend a good introductory books covering the topics of kinematics and dynamics? Thanks!",
    "posted_on": ISODate("2021-07-23T16:03:21Z"),
    "posted_by": ObjectId("612d1e835ebee16872a109a4")
}

新しいことに注意してください posted_by フィールドには、学生のドキュメントのオブジェクト識別子が含まれます。 これで、生徒のドキュメントには、投稿したメッセージに関する情報は含まれなくなります。

{
    "_id": ObjectId("612d1e835ebee16872a109a4"),
    "first_name": "Sammy",
    "last_name": "Shark",
    "emails": [
        {
            "email": "[email protected]",
            "type": "work"
        },
        {
            "email": "[email protected]",
            "type": "home"
        }
    ],
    "courses": [
        ObjectId("61741c9cbc9ec583c836170a"),
        ObjectId("61741c9cbc9ec583c836170b")
    ]
}

学生が書いたメッセージのリストを取得するには、メッセージコレクションでクエリを使用し、 posted_by 分野。 それらを別のコレクションに含めると、生徒のドキュメントに影響を与えることなく、メッセージのリストを安全に増やすことができます。

注:親参照を使用する場合、親ドキュメントを参照するフィールドにインデックスを作成すると、親ドキュメント識別子でフィルタリングするたびにクエリのパフォーマンスが大幅に向上する可能性があります。

関連ドキュメントの量に制限がない1対多の関係をモデル化する場合、ドキュメントに個別にアクセスする必要があるかどうかに関係なく、関連ドキュメントを個別に保存し、親参照を使用してそれらを親ドキュメントに接続することをお勧めします。 。

結論

ドキュメント指向データベースの柔軟性のおかげで、ドキュメントデータベースで関係をモデル化するための最良の方法を決定することは、リレーショナルデータベースよりも厳密な科学ではありません。 この記事を読むことで、ドキュメントを埋め込み、子と親の参照を使用して関連データを保存する方法を理解できました。 関係のカーディナリティを考慮し、無制限の配列を回避すること、およびドキュメントに個別にアクセスするか頻繁にアクセスするかを考慮することについて学習しました。

これらは、MongoDBで一般的な関係をモデル化するのに役立つガイドラインのほんの一部ですが、データベーススキーマのモデル化は万能ではありません。 スキーマを設計するときは、アプリケーションと、アプリケーションがデータをどのように使用および更新するかを常に考慮してください。

さまざまな種類のデータをMongoDBに格納するためのスキーマ設計と一般的なパターンの詳細については、そのトピックに関する公式MongoDBドキュメントを確認することをお勧めします。