NoSQLにおけるmany-to-manyの関連をモデリングするベストプラクティスについて

NoSQLにおけるmany-to-manyの関連をモデリングするベストプラクティスについてのまとめです。

## 概要

NoSQLでは製品によってACIDトランザクションやジョインに相当する処理がサポートされているもの、サポートされていないものに別れています。
以下、wikipediaの記事の引用となります。

https://en.wikipedia.org/wiki/NoSQL#ACID_and_join_support

|Database|ACID|Joins|
|:-:|:-:|:-:|
|Aerospike|Yes|No|
|AgensGraph|Yes|Yes|
|Apache Ignite|Yes|Yes|
|ArangoDB|Yes|Yes|
|Amazon DynamoDB|Yes|No|
|Couchbase|Yes|Yes|
|CouchDB|Yes|Yes|
|IBM Db2|Yes|Yes|
|InfinityDB|Yes|No|
|LMDB|Yes|No|
|MarkLogic|Yes|Yes|
|MongoDB|Yes|Yes|
|OrientDB|Yes|Yes|

しかしながら、ACIDトランザクションやジョインの操作がサポートされているかどうかにかかわらず、一般的にノードをまたがるジョインの操作の多様はパフォーマンスや可用性の観点で推奨されません。
NoSQLの製品ごとにmany-to-manyの関連を扱うベストプラクティスは以下のように案内されています。

## 各NoSQL製品の推奨事項

### Mongodb

https://www.mongodb.com/docs/manual/tutorial/model-embedded-many-to-many-relationships-between-documents/
>Create a data model that uses embedded documents to describe a many-to-many relationship between connected data. Embedding connected data in a single document can reduce the number of read operations required to obtain data. In general, structure your schema so your application receives all of its required information in a single read operation.

Mongodbではデータの一部を埋め込むことで、1リクエストですべてのデータを取得できる操作を推奨しています。

### DynamoDB

https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/bp-adjacency-graphs.html
>Adjacency lists are a design pattern that is useful for modeling many-to-many relationships in Amazon DynamoDB. More generally, they provide a way to represent graph data (nodes and edges) in DynamoDB.

DynamoDBではシングル・テーブル・デザインとグローバル・セカンダリ・インデックスの利用を拡張し、global secondary index-overloadingを活用する方法が推奨されています。
項目をまたがるprimary keyとsort keyの組み合わせを自由にマッピングする事により、任意の検索アクセス・パターンに対応します。

### Cassandra

https://cassandra.apache.org/doc/stable/cassandra/data_modeling/data_modeling_rdbms.html

>Relational modeling, in simple terms, means that you start from the conceptual domain and then represent the nouns in the domain in tables. You then assign primary keys and foreign keys to model relationships. When you have a many-to-many relationship, you create the join tables that represent just those keys. The join tables don’t exist in the real world, and are a necessary side effect of the way relational models work. After you have all your tables laid out, you can start writing queries that pull together disparate data using the relationships defined by the keys. The queries in the relational world are very much secondary. It is assumed that you can always get the data you want as long as you have your tables modeled properly. Even if you have to use several complex subqueries or join statements, this is usually true.
>
>By contrast, in Cassandra you don’t start with the data model; you start with the query model. Instead of modeling the data first and then writing queries, with Cassandra you model the queries and let the data be organized around them. Think of the most common query paths your application will use, and then create the tables that you need to support them.

Cassandraではジョインのサポートがないことから、ジョインの関連性を定義するテーブルを作成して手動でジョインを行うか、クエリのパターンに合わせてデータ・モデルを作成することが推奨されています。

### CosmosDB NoSQL

https://learn.microsoft.com/en-us/azure/cosmos-db/nosql/modeling-data

>In general, use embedded data models when:
>
>There are contained relationships between entities.
>There are one-to-few relationships between entities.
>There's embedded data that changes infrequently.
>There's embedded data that won't grow without bound.
>There's embedded data that is queried frequently together.
> Note
>Typically denormalized data models provide better read performance.
>
>
>Embedding data works nicely for many cases but there are scenarios when denormalizing your data cause more problems than it's worth. So what do we do now?
>
>Relational databases aren't the only place where you can create relationships between entities. In a document database, you might have information in one document that relates to data in other documents. We don't recommend building systems that would be better suited to a relational database in Azure Cosmos DB, or any other document database, but simple relationships are fine and might be useful.

モデリング方法としてembeddingと分割、双方のpros/consが解説されています。

### FireStore

https://firebase.google.com/docs/firestore/data-model

公式ドキュメントではデータ・モデルとしてHierarchical Dataのみを取り扱っています。

https://fireship.io/courses/firestore-data-modeling/relational-many-to-many/

非公式と思われるページでは、リファレンス(PKEY埋め込み)方式が提案されています。

### Couchbase

https://docs.couchbase.com/server/current/learn/data/document-data-model.html

>An individual document often represents a single instance of an object in application code. A document might be considered equivalent to a row in a relational table; with each of the document’s attributes being equivalent to a column. Couchbase, however, provides greater flexibility than relational databases, in that it can store JSON documents with varied schemas.
>
>Documents can contain nested structures. This allows developers to express many-to-many relationships without requiring a reference or junction table; and is naturally expressive of hierarchical data.

Couchbaseにおいて、many-to-manyの関連は階層構造で表現することが推奨されています。

## 各NoSQL製品におけるmany-to-manyの関連を扱う方法のまとめ

以上見てきたように、各NoSQL製品ではmany-to-manyの関連を扱う方法には一定のパターンが存在するようです。
以下はパターンを大まかに4つにまとめたものとなります。
それぞれ、主キーでアクセス可能な項目、主キーでのアクセス効率、アクセスの柔軟性、追加で発生するコスト(含むストレージや更新の発生 etc...)、整合性の維持等のトレードオフが存在することから、要件に応じて適宜選択することになります。

|パターン名|概要|pros|cons|データ・サイズの制限|項目数の制限|クエリのアクセス効率|アクセス・パスの柔軟性|整合性の維持|
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
|Embedded items|項目を他の項目に直接埋め込み、1項目として扱います。|読み取り、書き込みで1項目のみのアクセスで済み、パフォーマンスの向上が期待できます|1項目のサイズ制限の存在します、サブ項目への主キーでのアクセス等、異なるアクセス・パターンへの対応に問題があります。|あり|あり|1クエリで取得|親項目の主キーのみ|1項目へのアクセスのみを考慮すれば良いことから容易|
|Embedded primary keys|他の項目の主キーのみをドキュメントへ埋め込みます。|各項目に主キーでアクセスするなど、ジョイン以外の異なるアクセス・パターンへの対応が容易です。|かくｎ主キーの数が増大すると項目のサイズ制限に引っかかる可能性があります。複数項目をまたがる更新や削除を行う際に一貫性を取る必要があります。|あり|あり|ジョイン先の項目の取得に追加で1クエリが必要|各項目は主キーでアクセス可能|項目を跨る整合性の維持に注意が必要|
|Junction items|項目の関連付け、マッピングを行うための項目を別途定義します。|柔軟なアクセス・パターンに対応できます。無制限の関連数を扱えます。|マッピング専用の項目を用意することから、データの管理や更新、取得等のコストが追加で発生します。|なし|なし|ジョイン先の項目の取得に最低でも2クエリ以上必要|各項目は主キーでアクセス可能、複数のアクセス・パターンへの対応可能|項目を跨る整合性の維持に注意が必要|
|Materialized view|ジョインに相当する検索のパターンに対応可能なMaterialized viewを定義します。|1クエリで項目を跨る検索パターンを実現できます。Materialized viewの追加により、新しい検索パターンに対応できます。無制限の関連数を扱えます。|ビュー専用の項目を用意することから、データの管理や更新、取得等のコストが追加で発生します。特定のクエリに対応したビューの追加となることから汎用性が低いです。|なし|なし|1クエリで取得|各項目は主キーでアクセス可能、特定のジョインのパターンのみ対応可能|項目を跨る整合性の維持に注意が必要|

項目を埋め込んでしまうパターンがおそらく最も一般的かつ実装が容易で広く普及していると考えられます。
それ以外のパターンはそもそも更新や削除の頻度が少ないときに利用する、あるいは整合性の維持するための何らかの作り込みが求められると考えられます。

製品によってはネイティブでMaterialized viewやindex overloading、分散トランザクションなどの機能を備えているものの、RDBのようにある程度機能や振る舞いが標準化されているわけではなく、各機能は製品ごとに独自の仕様、振る舞いとなります。
したがって実現したいユースケースにほんとうに適したものであるか、細心の注意が必要です(名前は類似していても、振る舞いは全く異なる可能性があります)