DynamoDBでこれらの関係をモデル化する最良の方法は何ですか?
この質問のバリエーションを何度も目にしたので、Q&Aを書こうと思いました。
これを読む前に、次のことを理解しておく必要があります。
パスポートと人をモデル化して、この関係を実証できます。1つのパスポートは1人の所有者のみを持つことができ、1人は1つのパスポートしか持つことができません。
アプローチは非常に簡単です。2つのテーブルがあり、それらのテーブルの1つに外部キーが必要です。
パスポートテーブル:
パーティションキー:PassportId
╔════════════╦═══════╦════════════╗
║ PassportId ║ Pages ║ Issued ║
╠════════════╬═══════╬════════════╣
║ P1 ║ 15 ║ 11/03/2009 ║
║ P2 ║ 18 ║ 09/02/2018 ║
╚════════════╩═══════╩════════════╝
パスポートホルダーテーブル:
パーティションキー:PersonId
╔══════════╦════════════╦══════╗
║ PersonId ║ PassportId ║ Name ║
╠══════════╬════════════╬══════╣
║ 123 ║ P1 ║ Jane ║
║ 234 ║ P2 ║ Paul ║
╚══════════╩════════════╩══════╝
PersonIdがパスポートテーブルに表示されていないことに注意してください。そうすると、2つの場所に同じ情報(どのパスポートがどの人のものか)が表示されます。これにより、テーブルがどのパスポートの所有者であるかについて合意しなかった場合、追加のデータ更新と潜在的にいくつかのデータ品質の問題が発生します。
ただし、ユースケースがありません。PersonIdで個人を簡単に検索し、所有しているパスポートを見つけることができます。しかし、PassportIdがあり、それを所有している人を見つける必要がある場合はどうでしょうか。現在のモデルでは、パスポートホルダーテーブルでスキャンを実行する必要があります。これが通常の使用例である場合、スキャンを使用する必要はありません。GetItemをサポートするには、GSIをPassportホルダーテーブルに追加するだけです。
パスポートホルダーテーブルGSI:
パーティションキー:PassportId
╔════════════╦══════════╦══════╗
║ PassportId ║ PersonId ║ Name ║
╠════════════╬══════════╬══════╣
║ P1 ║ 123 ║ Jane ║
║ P2 ║ 234 ║ Paul ║
╚════════════╩══════════╩══════╝
これで、PassportIdまたはPersonIdを使用して、非常に迅速かつ安価に関係を検索できます。
これをモデル化するための他のオプションがあります。たとえば、外部キーのない「プレーン」なPassportテーブルとPersonテーブルを作成し、次にPassortIdsとPersonIdsを単純にマッピングする3番目の補助テーブルを作成できます。この場合、それが最もクリーンなデザインだとは思わないが、もし望むなら、そのアプローチには何の問題もない。これらは多対多関係セクションの補助関係テーブルの例であることに注意してください。
ペットと飼い主をモデル化して、この関係を実証できます。ペットの飼い主は1人だけですが、飼い主は多くのペットを持つことができます。
モデルは1対1モデルと非常によく似ているため、この違いにのみ焦点を当てます。
ペットテーブル:
パーティションキー:PetId
╔═══════╦═════════╦════════╗
║ PetId ║ OwnerId ║ Type ║
╠═══════╬═════════╬════════╣
║ P1 ║ O1 ║ Dog ║
║ P2 ║ O1 ║ Cat ║
║ P3 ║ O2 ║ Rabbit ║
╚═══════╩═════════╩════════╝
所有者テーブル:
パーティションキー:OwnerId
╔═════════╦════════╗
║ OwnerId ║ Name ║
╠═════════╬════════╣
║ O1 ║ Angela ║
║ O2 ║ David ║
╚═════════╩════════╝
多くのテーブルに外部キーを置きます。逆の方法で行って、PetIdをOwnerテーブルに入れると、1つのOwnerアイテムに一連のPetIdが必要になり、管理が複雑になります。
ペットの飼い主を見つけたい場合は、とても簡単です。我々は行うことができますGetItem関数をペットの項目を返すために、それは所有者が誰であるかを教えてくれる。しかし、逆の方法はより困難です。OwnerIdがある場合、どのペットが所有していますか?ペットテーブルでスキャンを実行する必要があるため、代わりにペットテーブルにGSIを追加します。
ペットテーブルGSI
パーティションキー:OwnerId
╔═════════╦═══════╦════════╗
║ OwnerId ║ PetId ║ Type ║
╠═════════╬═══════╬════════╣
║ O1 ║ P1 ║ Dog ║
║ O1 ║ P2 ║ Cat ║
║ O2 ║ P3 ║ Rabbit ║
╚═════════╩═══════╩════════╝
OwnerIdがあり、そのペットを検索したい場合は、ペットテーブルGSIでクエリを実行できます。たとえば、所有者O1に対するクエリは、PetId P1およびP2を持つアイテムを返します。
ここで何か面白いことに気づくかもしれません。主キーはテーブルに対して一意である必要があります。これは、ベーステーブルにのみ当てはまります。GSIプライマリキー(この場合はGSIパーティションキーのみ)は一意である必要はありません。
DynamoDBテーブルでは、各キー値は一意である必要があります。ただし、グローバルセカンダリインデックスのキー値は一意である必要はありません
補足として、GSIはベーステーブルと同じ属性のすべてを投影する必要はありません。ルックアップのみにGSIを使用している場合は、GSIキー属性のみを投影したい場合があります。
DynamoDBで多対多の関係をモデル化するには、主に3つの方法があります。それぞれに長所と短所があります。
医師と患者の例を使用して、この関係をモデル化できます。医師は多くの患者を持つことができ、患者は多くの医師を持つことができます。
一般的に、これは私の推奨するアプローチです。アイデアは、関係参照のない「プレーン」ベーステーブルを作成することです。リレーションシップ参照は、補助テーブルに入れられます(リレーションシップタイプごとに1つの補助テーブル-この場合、Doctors-Patientsのみ)。
ドクターテーブル:
パーティションキー:DoctorId
╔══════════╦═══════╗
║ DoctorId ║ Name ║
╠══════════╬═══════╣
║ D1 ║ Anita ║
║ D2 ║ Mary ║
║ D3 ║ Paul ║
╚══════════╩═══════╝
患者テーブル
パーティションキー:PatientId
╔═══════════╦═════════╦════════════╗
║ PatientId ║ Name ║ Illness ║
╠═══════════╬═════════╬════════════╣
║ P1 ║ Barry ║ Headache ║
║ P2 ║ Cathryn ║ Itchy eyes ║
║ P3 ║ Zoe ║ Munchausen ║
╚═══════════╩═════════╩════════════╝
DoctorPatientテーブル(補助テーブル)
パーティションキー:DoctorId
ソートキー:PatientId
╔══════════╦═══════════╦══════════════╗
║ DoctorId ║ PatientId ║ Last Meeting ║
╠══════════╬═══════════╬══════════════╣
║ D1 ║ P1 ║ 01/01/2018 ║
║ D1 ║ P2 ║ 02/01/2018 ║
║ D2 ║ P2 ║ 03/01/2018 ║
║ D2 ║ P3 ║ 04/01/2018 ║
║ D3 ║ P3 ║ 05/01/2018 ║
╚══════════╩═══════════╩══════════════╝
DoctorPatientテーブルGSI
パーティションキー:PatientId
ソートキー:DoctorId
╔═══════════╦══════════╦══════════════╗
║ PatientId ║ DoctorId ║ Last Meeting ║
╠═══════════╬══════════╬══════════════╣
║ P1 ║ D1 ║ 01/01/2018 ║
║ P2 ║ D1 ║ 02/01/2018 ║
║ P2 ║ D2 ║ 03/01/2018 ║
║ P3 ║ D2 ║ 04/01/2018 ║
║ P3 ║ D3 ║ 05/01/2018 ║
╚═══════════╩══════════╩══════════════╝
3つのテーブルがあり、DoctorPatient補助テーブルが興味深いテーブルです。
DoctorPatientベーステーブルの主キーは一意である必要があるため、DoctorId(パーティションキー)とPatientId(ソートキー)の複合キーを作成します。
DoctorIdを使用してDoctorPatientベーステーブルに対してクエリを実行し、Doctorが持つすべての患者を取得できます。
PatientIdを使用してDoctorPatient GSIでクエリを実行し、患者に関連付けられているすべての医師を取得できます。
このアプローチの長所は、テーブルを明確に分離できることと、単純なビジネスオブジェクトをデータベースに直接マップできることです。セットなどのより高度な機能を使用する必要はありません。
更新を調整する必要があります。たとえば、Patientを削除する場合は、DoctorPatientテーブルのリレーションシップも削除するように注意する必要があります。ただし、データ品質の問題が発生する可能性は、他のいくつかのアプローチと比較して低いです。
編集:DynamoDBはTransactionsをサポートするようになり、複数の更新を複数のテーブルにまたがる単一のアトミックトランザクションに調整できるようになりました。
このアプローチの潜在的な弱点は、3つのテーブルを必要とすることです。スループットでテーブルをプロビジョニングする場合、テーブルが多いほど、容量を分散する必要があるシンが少なくなります。ただし、新しいオンデマンド機能では、これは問題ではありません。
このアプローチでは、2つのテーブルのみを使用します。
ドクターテーブル:
パーティションキー:DoctorId
╔══════════╦════════════╦═══════╗
║ DoctorId ║ PatientIds ║ Name ║
╠══════════╬════════════╬═══════╣
║ D1 ║ P1,P2 ║ Anita ║
║ D2 ║ P2,P3 ║ Mary ║
║ D3 ║ P3 ║ Paul ║
╚══════════╩════════════╩═══════╝
患者テーブル:
パーティションキー:PatientId
╔═══════════╦══════════╦═════════╗
║ PatientId ║ DoctorIds║ Name ║
╠═══════════╬══════════╬═════════╣
║ P1 ║ D1 ║ Barry ║
║ P2 ║ D1,D2 ║ Cathryn ║
║ P3 ║ D2,D3 ║ Zoe ║
╚═══════════╩══════════╩═════════╝
このアプローチでは、関係を各テーブルにセットとして保存します。
DoctorのPatientsを見つけるには、DoctorテーブルでGetItemを使用してDoctorアイテムを取得できます。次に、PatientIdは、Doctor属性にセットとして格納されます。
患者の医師を検索するには、PatientテーブルでGetItemを使用して、Patientアイテムを取得できます。次に、DoctorIdsがセットとして患者属性に格納されます。
このアプローチの長所は、ビジネスオブジェクトとデータベーステーブルの間に直接マッピングがあることです。テーブルは2つしかないので、プロビジョニングスループット容量を使用している場合は、薄く分散する必要はありません。
このアプローチの主な欠点は、データ品質の問題の可能性です。患者を医師にリンクする場合、各テーブルに1つずつ、2つの更新を調整する必要があります。1つの更新が失敗した場合はどうなりますか?データが同期しなくなる可能性があります。
もう1つの欠点は、両方のテーブルでのセットの使用です。DynamoDB SDKはセットを処理するように設計されていますが、セットが含まれる場合、特定の操作が複雑になる可能性があります。
AWSではこれまで、これを隣接リストパターンと呼んでいました。より一般的には、グラフデータベースまたはトリプルストアと呼ばれます。
私は以前、AWS Adjanceyリストパターンでこの質問に回答しました。これは、一部の人々がそれを理解するのに役立ったようです。
そして、AWSによる最近のプレゼンテーションで、このパターンについてここで詳しく説明しています
このアプローチでは、すべてのデータを1つのテーブルに格納します。
テーブル全体ではなく、いくつかのサンプル行を描画しました。
パーティションキー:Key1
ソートキー:Key2
╔═════════╦═════════╦═══════╦═════════════╦══════════════╗
║ Key1 ║ Key2 ║ Name ║ illness ║ Last Meeting ║
╠═════════╬═════════╬═══════╬═════════════╬══════════════╣
║ P1 ║ P1 ║ Barry ║ Headache ║ ║
║ D1 ║ D1 ║ Anita ║ ║ ║
║ D1 ║ P1 ║ ║ ║ 01/01/2018 ║
╚═════════╩═════════╩═══════╩═════════════╩══════════════╝
そして、キーを反転するGSIが必要です。
パーティションキー:Key2
ソートキー:Key1
╔═════════╦═════════╦═══════╦═════════════╦══════════════╗
║ Key2 ║ Key1 ║ Name ║ illness ║ Last Meeting ║
╠═════════╬═════════╬═══════╬═════════════╬══════════════╣
║ P1 ║ P1 ║ Barry ║ Headache ║ ║
║ D1 ║ D1 ║ Anita ║ ║ ║
║ P1 ║ D1 ║ ║ ║ 01/01/2018 ║
╚═════════╩═════════╩═══════╩═════════════╩══════════════╝
このモデルには、特定の状況でいくつかの長所があります-高度に接続されたデータでうまく機能します。データを適切にフォーマットすると、非常に高速でスケーラブルなモデルを実現できます。スキーマやテーブルを更新せずに、エンティティや関係をテーブルに保存できるという点で柔軟性があります。スループット容量をプロビジョニングする場合は、アプリケーション全体のすべての操作ですべてのスループットを利用できるため、効率的です。
このモデルは、誤って使用したり、真剣に検討しなかったりすると、いくつかの大きな欠点に悩まされます。
ビジネスオブジェクトとテーブルの間の直接マッピングはすべて失われます。これにより、ほとんどの場合、スパゲッティコードが読めなくなります。単純なクエリでも、非常に複雑に感じる場合があります。コードとデータベースの間に明確なマッピングがないため、データ品質の管理は困難になります。このアプローチを使用するほとんどのプロジェクトは、データベースを管理するためだけにさまざまなユーティリティを作成し、その一部は独自の製品になります。
もう1つの小さな問題は、モデル内のすべてのアイテムのすべての属性が1つのテーブルに存在する必要があることです。これは通常、何百もの列を持つテーブルになります。それ自体は問題ではありませんが、多くの列を持つテーブルで作業しようとすると、通常、データの表示が困難になるなどの単純な問題が発生します。
要するに、AWSはおそらく一連の記事の中で有用な記事をリリースしたと思いますが、多対多の関係を管理するためのその他の(より単純な)概念を導入できなかったため、多くの人々が混乱しました。したがって、明確にするために、隣接リストパターンは有用ですが、DynamoDBで多対多の関係をモデル化するための唯一のオプションではありません。真剣にビッグデータなどの状況で機能する場合は、ぜひ使用してください。そうでない場合は、より単純なモデルの1つを試してください。
この記事はインターネットから収集されたものであり、転載の際にはソースを示してください。
侵害の場合は、連絡してください[email protected]
コメントを追加