NoSQLについて多くの書籍や記事がありますが、実際にアプリケーションのバックエンドとして使うときに必要となる知識についてはあまり紹介されていないように思います。内容が少し抽象的ではありますが、NOSQL DATA MODELING TECHNIQUESというNoSQLでのデータモデルに関する記事を見つけたので、要約をしてみました。
概説
- SQLとリレーショナルデータモデルはかなり昔に設計され、データベース内のデータ集計処理や並列実行性・一貫性・データ型検証に重きを置いていた。ところが、アプリケーションにはデータ集計をしなく、それなりに一貫性なども担保できるものが多くある。
- データ集計処理、並列実行性・一貫性・データ型検証を省くことで、パフォーマンスとスケーラビリティを大幅に向上でき、これにより生まれたのがNoSQLデータベースである。
- データモデルの進化
- Key-Value → Ordered Key-Value → Big Table → Document (+ Full Text Search)
- GraphデータベースはOrdered Key-Valueから派生した亜種と見なせる。
NoSQLでのデータモデル化に関する一般的な注釈
- リレーショナルデータモデルはデータ中心にモデル化することに対して、NoSQLデータモデルはアプリケーションにアクセスパターン中心にモデル化を行う。
- NoSQLデータモデルにはリレーショナルデータモデルよりも多くのデータ構造とアルゴリズムに関する理解が必要となる。
抽象的なテクニック
(1) 非正規化
- データの複製を複数の場所に置くことで、全体のデータ量が増える欠点があるものの、クエリ時にアクセスするデータ量が減り、クエリの処理を単純にでき、高速化ができる。
(2) 集約
- NoSQLは柔軟なスキーマを持つため、多対多のリレーションをドキュメントを入れ子にすることで表現したり、異なる属性を持つレコードも1つのテーブルに集約することができる。
(3) アプリケーションでの結合処理
- NoSQLではテーブル結合処理はめったにサポートされていなく、更新が多いデータには非正規化や集約による結合処理の実現が適さない。
- この場合は、アプリケーションでクエリ時に結合処理を行うことが適する。
一般的なモデル化テクニック
(4) アトミックな処理を行うための集約
- NoSQLデータベースではトランザクションのサポートは限定的であるが、アトミックな処理を行う必要があるデータを1レコードに集約することで一括で更新が可能となる。
(5) 順序を持つキー
- NoSQLデータベースには連続したIDを生成する機能を提供しているものがあり、この機能を利用することでレコードに順序を設定することができる。
(6) 次元削除
- Geohashのような次元削減アルゴリズムを使い多次元データを1次元のカラムに格納する。
(7) インデックステーブル
- インデックスをサポートしないシステムでは、検索対象カラム→IDというテーブルを作ることでインデックスを実現できる。
- ただしデータ更新時にアクセスする必要があるテーブルが多くなり、一貫性の問題が発生する。
(8) 複合キーによるインデックス
- 複数のキーを結合してインデックステーブルを作ることで、例えば2つのキーを結合した場合、1つ目のキーでの検索と、1つ目のキーと2つ目のキーでの検索ができる。
(9) 複合キーでの集約
- 複合キーをレコードのグループ化に使うことで、グループに属するデータをインデックスを使って取得してデータを集約するという処理を実現できる。
(10) インデックスでの検索と全件探索による集約
- データモデルというよりはデータ処理に関するパターン
- あるカテゴリに含まれるレコードの属性を集約したいときに、インデックスを使ってレコードのIDの集合を検索した後に、そのIDの集合に対して全件探索を行う。
階層構造をモデル化するテクニック
(11) 木構造の集約
- 木構造をJSON形式などで非正規化して1レコードに格納する。
- 木構造全体を一度で取得することは容易だが、更新や検索のコストは高い。
(12) 隣接リスト
- グラフ構造の各ノードを隣接するノードのIDと共に1レコードに格納する。
- 隣接ノードによるノードの検索はし易いが、大規模のグラフの探索は効率が悪い。
(13) マテリアライズド・パス
- 木構造などで各ノードのレコードにその親子ノードを含め非正規化して格納する。
- 木構造の一部を探索なしに取得でき、階層的なカテゴリを持つアイテムなどへの全文検索を行いたいときに便利である。
(14) 入れ子集合モデル
- 木構造などの入れ子になっている集合データをSQLデータベースで扱うためのテクニックであるが、NoSQLにも適用できる。
- リーフノードを配列にして格納し、ブランチノードについてリスト中の開始・終了位置のインデックスを作成する。
- あるノードの子ノードをグラフ探索なしにできるが、更新コストが高いため静的なデータに向く。
(15) 入れ子構造の平坦化:フィールド名に数字を付ける
- 同じフィールド名のドキュメントを入れ子にするドキュメントを1つのドキュメントに平坦化するために、フィールド名に数字を付け加える。
- 入れ子にしているドキュメントが多い場合はフィールドでの検索条件が複雑になる。
(16) 入れ子構造の平坦化:近接クエリ
- 同じフィールド名の子ドキュメントの値を連結することで1つのフィールドにまとめる。
- 連結した文字列で検索することでシンプルな条件式にできる。
(17) グラフのバッチ処理
- グラフデータベースは少数のノードへのアクセスには優れるがグラフ全体に対する処理にはスケールしない。
- NoSQLにグラフを格納してMapReduceのアルゴリズムを適用することで大規模なグラフに対する処理をスケールさせることができる。