対象読者
- SQLに慣れている方
- グラフデータベースに興味がある方
結論
Neo4jとは最も人気なOSSのグラフデータベース(以後「GDB」と書く)である(参考:DBランキング)。
リレーショナルデータベース(以後「RDB」と書く)とは、今も昔も最もポピュラーなデータベースである。
にも関わらず、なぜマイナーなGDBなぞを紹介するのか。
そんなRDBに物申すヤツらが次世代のDBと
開発業務の大部分が価値創出へ繋がる本質的な仕事ではなく、リレーショナルデータベースに費やささざるを得ない。そんな現状に絶望した者たちによってNeo4jは開発された。
Cypherの哲学
非プログラマであっても簡単かつ直感的に理解できる。
グラフの落書きのアスキアート
なぜRDBによって業務が逼迫するのだろうか。その主な理由は以下の2つだ。
- テーブルの依存関係を理解するコストが高い(ドメインモデルとテーブル表現のインピーダンスミスマッチ)
- テーブル結合によるクエリのパフォーマンス低下への対応(パフォーマンスチューニング)
関係は避けられない
ドメイン
現実世界は無限かつ連続的であり、人間の認識能力を超える。そこで、現実世界から特定の部分(ドメイン=業務知識)に着目し、さらにそこから要素とそれらの依存関係を抽出する。こうしてドメインモデルを作成する。
RDBでは、たとえば顧客の氏名や性別、生年月日などのように意味的にまとまりがある要素をテーブルとして分類する。
人工的なキー
結合表
正規化、非正規化によってドメインモデルは
それらのテーブルは独立しているのではなく関連がある。テーブル結合(join)することではじめて意味のあるデータとなる。
しかし、テーブル定義を眺めてもは難しい。アプリケーションのソースコード(特にSQL文)を読むときにはじめてテーブル間のつながりや全体像を理解することができる。
しかしテーブル結合はアプリケーション
リレーショナルモデリングのやり方
具体的な1行のデータが挿入される前にモデリングを完璧に終えている必要がある。これはウォーターフォール的かつ非現実的な方法である。
設計-正規化-非正規化
フェーズ1.ドメインモデルの決定
以下について有識者で議論し合意を取る。ホワイトボードにグラフを描いて議論を進めることが多い。
- ドメインのエンティティは何か
- それらエンティティ同士の依存関係関係
- 状態遷移を司るルール
フェーズ2.ドメインモデルからER図への変換
次は、合意が取れたドメインモデルをより形式的な表現、つまりER図に変換する。ここでホワイトボードに描かれた最も新鮮な一次情報は捨てられることになる。
ER図の関係には重合度(カーディナリティ)や名前を書くことができるが、方向は記述できない。さらには、ER図をテーブル設計に落とし込むときには
ER図(Entitiy Relationship Diagram)とは、カーディナリティを含んだ関係によってエンティティ同士をつなげた図である。
エンティティがどんな属性を持つかなどの細かい詳細は
フェーズ3.ER図をもとに正規化されたテーブルへ変換
ER図はほぼそのままテーブル設計に対応するが、まだやることがある
1対多関係は外部キー制約
多対多関係は結合表
ここでもホワイトボードに描かれていたときの明瞭さは失われつつある。
フェーズ4.非正規化
データベースに最適化するように
適切なドメインモデルの表現を捨て去り、
RDBMSの専門的知識が必要
かなりの工数を要する。運用中に再設計すべき機会は必ず訪れるが、その都度再設計することは非現実的である。
マイグレーション:DBのリファクタリングを非正規化が困難なものにする
リレーショナルデータベースの限界
-- ボブの友達は誰か(正クエリ)
-- SQL
SELECT p1.name
FROM Person p1
JOIN PersonFriend
ON PersonFriend.friend_id = p1.id
JOIN Person p2
ON PersonFriend.person_id = p2.id
WHERE p2.name = 'Bob';
-- Cypher
MATCH (p1:Person)-[:FRIEND_OF]->(p2:Person)
WHERE p1.name = 'Bob'
RETURN p2.name
-- 誰がボブの友達か(逆クエリ)
-- SQL
SELECT p1.name
FROM Person p1
JOIN PersonFriend
ON PersonFriend.person_id = p1.id
JOIN Person p2
ON PersonFriend.friend_id = p2.id
WHERE p2.name = 'Bob';
-- Cypher
MATCH (p1:Person)<-[:FRIEND_OF]-(p2:Person)
WHERE p1.name = 'Bob'
RETURN p2.name
-- アリスの友達の友達
-- SQL
SELECT
p1.name AS PERSON,
p2.name AS FRIEND_OF_FRIEND
FROM PersonFriend pf1
JOIN Person p1
ON pf1.person_id = p1.id
JOIN PersonFriend pf2
ON pf2.person_id = pf1.friend_id
JOIN Person p2
ON pf2.friend_id = p2.id
WHERE p1.name = 'Alice'
AND pf2.friend_id <> p1.id
-- Cypher
MATCH (p1:Person)-[:FRIEND_OF*2]->(p2:Person)
WHERE p1.name = 'Alice'
RETURN p2.name
テーブル間のつながりを明示する手段がない(外部参照制約はあるけど、把握しづらい)
Nullチェックの苦しみ
逆クエリ「(正クエリ:「顧客がどの商品を購入したか、に対する逆クエリは「この商品を購入したっ顧客は誰か): 正クエリはカンタン。逆クエリは総当たりスキャンが必要O(n) 対して、GDBはO(1)
再帰的質問「商品Aを購入した顧客の内、商品Bも購入した者は誰か」のコストの高さとSQLの複雑さ
アリスの友達の友達
他のNoSQL(ドキュメント型、カラムファミリー型、key-value型)も関係扱うの高コスト(keyとなるプロパティを持たせる = 外部キーを持たせることと同義)
外部キーは整合性を保つ努力が必要
つながりのないデータの走査はRDBの方が速い
本当に必要なのは、要素間のつながりを含むまとまりのある全体像
インデックスなし隣接性 index-free adjacency
低コストな結合のための肝。
SQLのjoinをCypherではどう表現するのか
RDBとGDBの構成要素の比較
テーブル ノードのラベル
レコード ノード
関係 join エッジ
SQLとCypherのCRUD比較
INSERT CREATE
SELECT MATCH
UPDATE
DELETE
参考
- グラフデータベース ―Neo4jによるグラフデータモデルとグラフデータベース入門
コメント