* 目次 [#be7fea81] * 目次 [#j5b2c0ea] #contents * Neo4jでシステムダウン!グラフデータベース選択の失敗談と安全な代替案 [#e224366a] * Neo4jでシステムダウン!グラフデータベース選択の失敗談と安全な代替案 [#o5094f97] ** はじめに [#xf7a7b08] ** はじめに [#n695770a] グラフデータベースの導入を検討中に、Neo4jで手痛い失敗を経験しました。1GB未満のデータでシステムがダウンし、CPU使用率が180%に達する事態に。この記事では、その失敗談と調査した安全な代替案をシェアします。 ** 失敗談:Neo4jの落とし穴 [#o5ce9cc5] ** 失敗談:Neo4jの落とし穴 [#o5a149bb] *** 何が起こったか [#k0a9d567] *** 何が起こったか [#z0355f52] - データサイズ: 1GB未満 - 現象: 少しでもループするクエリでシステム全体がダウン - CPU使用率: 180%(Dockerで確認) - 復旧: ループ解除すらできない状態 *** 根本原因 [#s19b2a32] *** 根本原因 [#c8b760ed] Neo4jは確かに強力ですが、''セーフではない設計''が問題でした: - 無限ループの検知機能が不十分 - メモリ制限の設定が複雑 - クエリタイムアウトのデフォルト設定が甘い - リソース消費量が予想以上に大きい # このようなクエリで簡単にシステムダウン MATCH (a)-[*]-(b) RETURN a, b # 深度制限なしの全探索で即死 ** 調査した代替案と比較 [#b31a5fb8] ** グラフクエリ言語の基礎知識 [#b966f677] *** PostgreSQL + AGE拡張(最推奨) [#e7657eaa] グラフデータベースを選ぶ前に、主要なクエリ言語について理解しておきましょう。~ *** Cypherとは? [#o0c5b01c] Cypherは''Neo4j発祥のグラフクエリ言語''で、SQLに似た読みやすい構文が特徴です:~ -- Cypher例:友達の友達を検索 MATCH (me:Person {name: 'Alice'})-[:FRIENDS]->(friend)-[:FRIENDS]->(fof) WHERE fof <> me RETURN DISTINCT fof.name ''Cypherの特徴'':~ - パターンマッチング重視の構文~ - ASCII アート風の関係表現 `(a)-[:関係]->(b)`~ - SQLユーザーには親しみやすい~ - 直感的で読みやすい~ *** Gremlinとは? [#h05d12d3] Gremlinは''Apache TinkerPop''のグラフトラバーサル言語で、プログラミング的なアプローチが特徴です:~ // Gremlin例:同じ検索をトラバーサルで表現 g.V().has('name', 'Alice') .out('friends') .out('friends') .where(neq('Alice')) .dedup() .values('name') ''Gremlinの特徴'':~ - ステップバイステップのトラバーサル~ - プログラマー向けの関数型アプローチ~ - 複数のデータベースで標準サポート~ - 複雑な処理に向いている~ *** CypherとGremlinの違い [#o9e0d2fa] |項目|Cypher|Gremlin|h |''書き方''|宣言的(SQLライク)|手続き的(プログラムライク)| |''学習コスト''|SQLユーザーには易しい|プログラマーには自然| |''表現力''|パターンマッチングが得意|複雑なロジックが得意| |''対応DB''|Neo4j、PostgreSQL+AGE|多数のグラフDB| |''可読性''|非常に高い|慣れが必要| *** 実際の例で比較 [#ad08af45] 同じ「30歳以上のユーザーとその友達」を検索する場合:~ -- Cypher版(直感的) MATCH (user:Person)-[:FRIENDS]->(friend:Person) WHERE user.age >= 30 RETURN user.name, friend.name // Gremlin版(ステップ的) g.V().hasLabel('Person') .has('age', gte(30)) .out('friends') .path() .by('name') どちらも同じ結果ですが、''Cypherの方が SQL に慣れた人には読みやすく''、''Gremlinの方がプログラマーには柔軟''です。~ ** 調査した代替案と比較 [#ka6f83d5] *** PostgreSQL + AGE拡張(最推奨) [#l24f36fe] ''安全性'': ⭐⭐⭐⭐⭐~ ''Cypher互換性'': ⭐⭐⭐⭐⭐~ ''リソース消費'': ⭐⭐⭐⭐⭐ -- Cypherがそのまま使える SELECT * FROM cypher('graph', $$ MATCH (a:Person)-[:FRIENDS]->(b:Person) WHERE a.age > 25 RETURN a.name, b.name $$) AS (person1 agtype, person2 agtype); ''メリット'': - Neo4jの1/10のメモリ使用量 - 枯れたPostgreSQLベース - Cypherクエリがほぼそのまま使える - 軽量Docker運用可能 - Git管理しやすい *** NetworkX + SQLite [#c79fb28f] *** NetworkX + SQLite [#i8859d26] ''安全性'': ⭐⭐⭐⭐⭐~ ''学習コスト'': ⭐⭐⭐⭐⭐~ ''制御しやすさ'': ⭐⭐⭐⭐⭐ import networkx as nx import sqlite3 # 完全制御可能な安全設計 class SafeGraphProcessor: def __init__(self, max_memory_mb=500): self.max_memory = max_memory_mb def safe_shortest_path(self, G, source, target): if len(G.nodes()) > 100000: # ノード数制限 raise ValueError("グラフサイズ上限超過") return nx.shortest_path(G, source, target) ''メリット'': - プロセス分離で安全 - Pythonの豊富なエコシステム - SQLiteでGit管理可能 - 学習コストが最小 *** ArangoDB [#r5a53f01] *** ArangoDB [#gdbc67c4] ''安全性'': ⭐⭐⭐⭐~ ''商用実績'': ⭐⭐⭐⭐~ ''マルチモデル'': ⭐⭐⭐⭐⭐ -- AQL(Cypher風だがより制御しやすい) FOR vertex, edge, path IN 1..3 OUTBOUND 'Person/alice' Friends FILTER vertex.age > 25 LIMIT 1000 -- 結果数制限が簡単 RETURN vertex.name ** マイクロサーバ分散アーキテクチャ [#d4d0bdb4] ** マイクロサーバ分散アーキテクチャ [#l64bd940] 100万ノード規模への対応として、マイクロサーバ分散を検討: サーバA: ユーザー関係グラフ (10万ノード) サーバB: 商品関係グラフ (30万ノード) サーバC: カテゴリ・タググラフ (5万ノード) ''メリット'': - 障害の影響範囲を限定 - 各サーバの負荷制御が可能 - スケールアウト対応 ** 循環グラフ安全性の比較 [#h8b90dbf] ** 循環グラフ安全性の比較 [#jfd59085] |データベース|循環グラフ耐性|システム安全性|メモリ使用量|h |Neo4j|❌ 非常に危険|❌ システムダウン|❌ 非常に高い| |PostgreSQL+AGE|⭐⭐⭐⭐ 深度制限可能|⭐⭐⭐⭐ 枯れた技術|⭐⭐⭐⭐ 低い| |NetworkX|⭐⭐⭐⭐⭐ 完全制御可能|⭐⭐⭐⭐⭐ プロセス分離|⭐⭐⭐ 中程度| |ArangoDB|⭐⭐⭐⭐ 制限機能あり|⭐⭐⭐⭐ タイムアウト可能|⭐⭐⭐⭐ 低い| ** PostgreSQLのGremlin対応について [#q6642efc] ** PostgreSQLのGremlin対応について [#jd0d551e] 実はPostgreSQLでもGremlin言語が使用可能です: # Python Gremlin + PostgreSQL from gremlin_python.structure.graph import Graph from gremlin_python.driver.driver_remote_connection import DriverRemoteConnection # AGEデータをGremlin風にアクセス def postgres_to_gremlin(): # PostgreSQLからデータ取得 conn = psql.connect("postgresql://localhost/graph_db") # Gremlin風のトラバーサル g = Graph().traversal() result = g.V().has('name', 'alice').out('knows').toList() return result ** 実際のクエリ比較 [#u815e2ca] ** 実際のクエリ比較 [#ua1345a2] 同じ結果を得るための3つの書き方: -- AGE Cypher風 SELECT * FROM cypher('graph', $$ MATCH (a)-[:KNOWS]->(b) RETURN a.name, b.name $$); -- Gremlin風(同じデータ) g.V().outE('knows').inV().path().by('name') -- 純粋SQL(同じ結果) SELECT a.name, b.name FROM nodes a JOIN edges e ON a.id = e.source_id JOIN nodes b ON e.target_id = b.id WHERE e.label = 'knows'; ** Docker運用でのリソース比較 [#i616cb1d] ** Docker運用でのリソース比較 [#lef032ea] 実測したメモリ使用量: # Neo4j docker stats # CONTAINER CPU % MEM USAGE / LIMIT MEM % # neo4j 180% 2.5GiB / 4.0GiB 62.5% # PostgreSQL + AGE docker stats # CONTAINER CPU % MEM USAGE / LIMIT MEM % # postgres 15% 200MiB / 4.0GiB 5.0% 軽量Docker設定例: # PostgreSQL + AGE の軽量運用 docker run -d \ -p 5432:5432 \ -e POSTGRES_PASSWORD=password \ apache/age-postgres:latest # メモリ使用量: ~100-200MB(neo4jの1/10以下) ** Git管理との親和性 [#lcae0777] ** Git管理との親和性 [#yb69af91] SQLiteベースならGitでバージョン管理が可能: # データベースファイルをGitで管理 git add graph_data.db git commit -m "ユーザー関係グラフ更新" git push origin main # 問題発生時の即座復旧 git checkout HEAD~1 graph_data.db ** 結論:安全第一の選択 [#k2a66bee] ** 結論:安全第一の選択 [#l09d610a] Neo4jでの痛い経験から学んだ教訓:~ + ''PostgreSQL + AGE'': Cypher使いたいならこれ一択~ + ''NetworkX + SQLite'': 最も安全で制御しやすい~ + ''ArangoDB'': 商用実績重視なら~ 特に''PostgreSQL + AGE''は:~ - リソース消費が1/10~ - Cypherクエリがそのまま使える~ - 軽量Docker運用~ - Git管理も簡単~ - Gremlin対応も可能~ ** 次のステップ [#zd5b0914] ** 次のステップ [#d4c307b8] 失敗を糧に、今度は安全性を最優先にした設計で進めていきます。同じような問題で悩んでいる方の参考になれば幸いです。~ 循環グラフでシステムダウンするリスクを避けるためには、''制御可能な仕組み''を選ぶことが重要だと痛感しました。~ &color(red){''重要'': グラフデータベース選択では、機能性だけでなく安全性とリソース効率を最優先に検討することをお勧めします。};~ ---- ''この記事があなたのグラフデータベース選択の助けになりましたか?コメントやフィードバックをお待ちしています!''