🕸️
概念 #データ設計 #グラフDB #Neo4j #Cypher #グラフアルゴリズム #DDIA 📚 データ志向アプリケーション設計(DDIA)

グラフDB深掘り

Neo4jの物理的なインデックスフリー隣接の仕組み、Cypherクエリのパターン、PageRank・最短経路などグラフアルゴリズムの実装と用途を理解する

定義

グラフDBはノード(頂点)とエッジ(辺)でデータを表現し、複雑な関係性のトラバーサル(経路探索)に特化したDB。RDBでは再帰的なJOINが必要な「深さ不定の関係性探索」を自然に表現できる。

なぜグラフDBが必要か

-- RDBで「友達の友達を全員取得」(SNSのつながり)
-- 深さが固定なら書けるが...
SELECT DISTINCT u3.*
FROM users u1
JOIN friendships f1 ON f1.user_id = u1.id
JOIN users u2 ON u2.id = f1.friend_id
JOIN friendships f2 ON f2.user_id = u2.id
JOIN users u3 ON u3.id = f2.friend_id
WHERE u1.id = 1;

-- 深さが可変(「最大N度のつながり」)は再帰CTEが必要
WITH RECURSIVE connections AS (
  SELECT friend_id, 1 AS depth FROM friendships WHERE user_id = 1
  UNION ALL
  SELECT f.friend_id, c.depth + 1
  FROM friendships f JOIN connections c ON f.user_id = c.friend_id
  WHERE c.depth < 6  -- LinkedIn的な「6次の隔たり」
)
SELECT DISTINCT friend_id FROM connections;
-- → クエリが複雑、大規模グラフでは非常に遅い

グラフDBではこれが数行で書ける。

Neo4j のインデックスフリー隣接

Neo4jの最大の特徴。各ノードが隣接するノードへの直接ポインタ(物理的なアドレス)を持つ。

RDBのJOIN:
  user(id=1) → friendship(user_id=1) をインデックスで検索 → O(log N)
  → 深さが増えるたびにインデックス検索を繰り返す

Neo4jのトラバーサル:
  node(id=1) → ポインタを辿るだけ → O(1) per hop
  → グラフの大きさに関係なく、トラバーサル速度は一定
ノードの物理構造:
  [nodeId | labels | firstRelationshipId | firstPropertyId]

リレーションシップの物理構造:
  [relId | type | startNodeId → nextRelFromStart | endNodeId → nextRelFromEnd | firstPropertyId]

全ノードをスキャンするのではなく、ポインタを辿るだけで関連ノードに到達できる。

Cypher クエリ言語

// ノードとリレーションシップの作成
CREATE (alice:Person {name: 'Alice', age: 30})
CREATE (bob:Person {name: 'Bob', age: 25})
CREATE (alice)-[:KNOWS {since: 2020}]->(bob)

// パターンマッチング
MATCH (p:Person {name: 'Alice'})-[:KNOWS]->(friend)
RETURN friend.name

// 可変長パス(1〜3ホップ)
MATCH (alice:Person {name: 'Alice'})-[:KNOWS*1..3]->(person)
RETURN DISTINCT person.name

// 最短経路
MATCH (alice:Person {name: 'Alice'}), (dave:Person {name: 'Dave'})
MATCH path = shortestPath((alice)-[:KNOWS*]-(dave))
RETURN path, length(path)

よく使うパターン

// 共通の友達
MATCH (alice:Person {name: 'Alice'})-[:KNOWS]->(friend)<-[:KNOWS]-(bob:Person {name: 'Bob'})
RETURN friend.name

// 商品レコメンデーション(協調フィルタリング)
MATCH (user:User {id: 1})-[:PURCHASED]->(product)<-[:PURCHASED]-(other:User)
      -[:PURCHASED]->(recommended)
WHERE NOT (user)-[:PURCHASED]->(recommended)
RETURN recommended.name, COUNT(*) AS score
ORDER BY score DESC
LIMIT 10

グラフアルゴリズム

Neo4j Graph Data Science(GDS)ライブラリで実装済みのアルゴリズムを活用できる。

PageRank

Webのリンク構造から「重要なページ」を計算するアルゴリズム(Googleの基盤)
ソーシャルグラフでは「影響力のあるユーザー」の特定に使う

直感: 多くの人からフォローされ、かつフォロワー自身も多くフォローされているユーザーが高スコア
// Neo4j GDSでPageRankを計算
CALL gds.pageRank.stream('myGraph')
YIELD nodeId, score
RETURN gds.util.asNode(nodeId).name AS name, score
ORDER BY score DESC
LIMIT 10

コミュニティ検出(Louvain法)

類似したノード群(クラスター)を自動検出
SNSのコミュニティ、不正グループの検出に使われる

直感: 内部のエッジが多く、外部のエッジが少ないグループを見つける
CALL gds.louvain.stream('myGraph')
YIELD nodeId, communityId
RETURN gds.util.asNode(nodeId).name AS name, communityId
ORDER BY communityId

最短経路(Dijkstra / A*)

// 重み付き最短経路(Dijkstra)
MATCH (source:Location {name: 'Tokyo'})
MATCH (target:Location {name: 'Osaka'})
CALL gds.shortestPath.dijkstra.stream('myGraph', {
  sourceNode: source,
  targetNode: target,
  relationshipWeightProperty: 'distance'
})
YIELD path, totalCost
RETURN path, totalCost

中心性指標

Degree Centrality:  エッジの多いノード(ハブ)
Betweenness:        多くの最短経路が通るノード(ブリッジ)
Closeness:          他ノードへの平均距離が短いノード

グラフDBの主な用途

用途詳細
不正検知複数口座間の送金ネットワークで不正グループを検出
レコメンデーション協調フィルタリング、「あなたへのおすすめ」
ナレッジグラフGoogle Knowledge Graph、Wikidata
アクセス制御RBAC(Role-Based Access Control)のロール継承
サプライチェーン部品の依存関係、影響範囲の分析
経路探索カーナビ、物流最適化

RDB + グラフDB のハイブリッド

実務ではすべてをグラフDBに移すのではなく、用途別に使い分ける。

PostgreSQL(メインのビジネスデータ):
  users, orders, products, payments
  
Neo4j(関係性の探索に特化):
  ソーシャルグラフ、レコメンデーション
  
同期方法:
  PostgreSQLへの書き込み → CDCでKafkaに流す
  KafkaのConsumerがNeo4jに書き込む

AGE(PostgreSQLのグラフ拡張)

PostgreSQLのままグラフクエリを書ける拡張。Apacheプロジェクト。

-- PostgreSQLでCypherを実行
SELECT * FROM cypher('my_graph', $$
  MATCH (v:Person)-[e:KNOWS]->(v2:Person)
  RETURN v.name, v2.name
$$) AS (person1 agtype, person2 agtype);

既存のPostgreSQLスタックにグラフ機能を追加したい場合に有用。

関連概念

出典・参考文献

  • Martin Kleppmann, Designing Data-Intensive Applications (2017) Chapter 2
  • Neo4j Documentation — neo4j.com/docs
  • Neo4j Graph Data Science Library — neo4j.com/docs/graph-data-science
  • Apache AGE — age.apache.org
  1. 1. 🗄️データ志向アプリケーション設計:概要
  2. 2. 🧩データモデルとクエリ言語
  3. 3. 💾ストレージエンジンとインデックス
  4. 4. 🔁レプリケーション
  5. 5. 🍕パーティショニング(シャーディング)
  6. 6. 🔒トランザクションとACID
  7. 7. 分散システムの本質的な問題
  8. 8. 🤝一貫性と分散合意
  9. 9. 📦バッチ処理
  10. 10. 🌊ストリーム処理
  11. 11. 📋エンコーディングとスキーマ進化
  12. 12. 🔗Sagaパターンと分散トランザクション
  13. 13. 🏗️データシステムの統合設計
  14. 14. 📸MVCC(多版型同時実行制御)
  15. 15. 📊列指向ストレージとOLAP設計
  16. 16. 🕰️ベクタークロックと因果順序
  17. 17. 🔀CRDT(競合なし複製データ型)
  18. 18. 🔍クエリオプティマイザーと実行計画
  19. 19. キャッシュ戦略とRedis設計
  20. 20. 🔎全文検索と転置インデックス
  21. 21. 🌐NewSQL(分散ACIDデータベース)
  22. 22. 📝WALと論理レプリケーション
  23. 23. 🔌コネクションプーリング
  24. 24. 🚧ゼロダウンタイムマイグレーション
  25. 25. 🆔分散ID生成
  26. 26. 🔄N+1問題とDataLoaderパターン
  27. 27. 📈タイムシリーズDB
  28. 28. 🛡️Row Level Security(行レベルセキュリティ)
  29. 29. 📤Outboxパターン(トランザクショナルアウトボックス)
  30. 30. 💾DBバックアップとPITR
  31. 31. ⚠️データベース設計アンチパターン
  32. 32. 🕸️グラフDB深掘り
  33. 33. 🔋バックプレッシャーとサーキットブレーカー

出典: Martin Kleppmann, 'Designing Data-Intensive Applications' (2017) Chapter 2