* 目次 [#g47abac4]
#contents

** はじめに [#oe822bd5]

Neo4jでの循環参照によるシステムダウンを経験した後、より安全で運用しやすいグラフデータベース環境を求めて、PostgreSQL + AGE拡張による代替環境の構築に取り組みました。本記事では、Docker環境での構築から安全性検証まで、実際の手順と検証結果を詳しく紹介します。

** なぜPostgreSQL + AGEなのか [#o963d6f3]

*** Neo4jの課題 [#zcdb0037]
- JVMヒープメモリ枯渇によるシステム全体のクラッシュ
- 循環参照時の予測困難なメモリ使用量
- 深い探索クエリでの無限ループ的な挙動
- 企業環境での運用ノウハウの不足

*** PostgreSQL + AGEの利点 [#n7874255]
- PostgreSQLの20年以上の運用実績と安定性
- 厳格なメモリ・リソース制御機能
- プロセス分離による障害の局所化
- Cypherクエリ言語のサポート(Neo4jとの互換性)
- 豊富な監視・運用ツール

** 環境構築 [#v51eecec]

*** 必要なファイル構造 [#ta42bc81]

 project/
 ├── docker-compose.yml
 ├── init-scripts/
 │   └── init-age.sql
 └── examples/
     └── age_examples.sql

*** Docker Compose設定 [#u88f25c3]

docker-compose.ymlファイルを作成します:

 version: '3.8'
 
 services:
   postgres-age:
     image: apache/age:latest
     container_name: postgres-age
     environment:
       POSTGRES_DB: agedb
       POSTGRES_USER: ageuser
       POSTGRES_PASSWORD: agepass
       POSTGRES_HOST_AUTH_METHOD: trust
     ports:
       - "5432:5432"
     volumes:
       - postgres_age_data:/var/lib/postgresql/data
       - ./init-scripts:/docker-entrypoint-initdb.d/
     restart: unless-stopped
     healthcheck:
       test: ["CMD-SHELL", "pg_isready -U ageuser -d agedb"]
       interval: 10s
       timeout: 5s
       retries: 5
 
   # オプション: pgAdmin(Webベースの管理ツール)
   pgadmin:
     image: dpage/pgadmin4:latest
     container_name: pgadmin-age
     environment:
       PGADMIN_DEFAULT_EMAIL: admin@example.com
       PGADMIN_DEFAULT_PASSWORD: admin
     ports:
       - "8080:80"
     depends_on:
       - postgres-age
     restart: unless-stopped
 
 volumes:
   postgres_age_data:

*** AGE拡張初期化スクリプト [#a72045a0]

init-scripts/init-age.sqlファイルを作成します:

 -- AGE拡張の有効化
 CREATE EXTENSION IF NOT EXISTS age;
 
 -- age_graphスキーマをパスに追加
 SET search_path = ag_catalog, "$user", public;
 
 -- サンプルグラフの作成
 SELECT create_graph('sample_graph');
 
 -- 基本的なセットアップ関数
 CREATE OR REPLACE FUNCTION setup_age_environment()
 RETURNS void AS $$
 BEGIN
     -- AGEが正常にインストールされているかチェック
     IF NOT EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'age') THEN
         RAISE EXCEPTION 'AGE extension is not installed';
     END IF;
     
     -- グラフが作成されているかチェック
     IF NOT EXISTS (SELECT 1 FROM ag_catalog.ag_graph WHERE name = 'sample_graph') THEN
         PERFORM create_graph('sample_graph');
     END IF;
     
     RAISE NOTICE 'AGE environment setup completed successfully';
 END;
 $$ LANGUAGE plpgsql;
 
 -- セットアップ関数を実行
 SELECT setup_age_environment();

*** 環境の起動 [#ua52cf3d]

 mkdir postgres-age-project
 cd postgres-age-project
 mkdir init-scripts examples
 
 # ファイルを配置した後
 docker-compose up -d

*** 接続確認 [#j8807ed7]

 docker exec -it postgres-age psql -U ageuser -d agedb
 
 # AGE拡張の確認
 \dx

期待される出力:

 agedb=# \dx
                  List of installed extensions
   Name   | Version |   Schema   |         Description
 ---------+---------+------------+------------------------------
  age     | 1.5.0   | ag_catalog | AGE database extension
  plpgsql | 1.0     | pg_catalog | PL/pgSQL procedural language
 (2 rows)

** MCP (Model Context Protocol) 接続設定 [#id79c828]

*** 前提条件 [#jddc8ad0]

 npm install -g @modelcontextprotocol/server-postgres

*** Claude Desktop設定 [#i4a9ac65]

Windows: %APPDATA%\Claude\claude_desktop_config.json

 {
   "mcpServers": {
     "postgres-age": {
       "command": "npx",
       "args": [
         "@modelcontextprotocol/server-postgres",
         "postgresql://ageuser:agepass@localhost:5432/agedb"
       ],
       "env": {
         "POSTGRES_CONNECTION_STRING": "postgresql://ageuser:agepass@localhost:5432/agedb"
       }
     }
   }
 }

*** Windsurf設定 [#w2c6bc50]

Windows: %APPDATA%\Windsurf\User\settings.json

 {
   "mcp": {
     "servers": {
       "postgres-age": {
         "command": "npx",
         "args": [
           "@modelcontextprotocol/server-postgres",
           "postgresql://ageuser:agepass@localhost:5432/agedb"
         ],
         "env": {
           "POSTGRES_CONNECTION_STRING": "postgresql://ageuser:agepass@localhost:5432/agedb"
         }
       }
     }
   }
 }

** AGE使用時の重要なポイント [#z24c96d9]

*** 基本設定(毎回必須) [#e6c8ad17]

AGEを使用する際は、毎回以下の設定が必要です:

 LOAD 'age';
 SET search_path = ag_catalog, "$user", public;

*** 型指定の注意点 [#xf88401f]

Cypherクエリでは、戻り値の型を必ず ag_catalog.agtype と明示的に指定する必要があります:

 -- ❌ 間違い
 SELECT * FROM cypher('sample_graph', $$
     MATCH (n) RETURN n
 $$) AS (node agtype);
 
 -- ✅ 正しい
 SELECT * FROM cypher('sample_graph', $$
     MATCH (n) RETURN n
 $$) AS (node ag_catalog.agtype);

*** 基本的なクエリ例 [#fd69fe4d]

**** ノードの検索 [#ba54cd5c]

 SELECT * FROM ag_catalog.cypher('sample_graph', $$
     MATCH (n:Person) 
     RETURN n.name, n.age
 $$) AS (name ag_catalog.agtype, age ag_catalog.agtype);

**** リレーションシップの検索 [#k04c8944]

 SELECT * FROM ag_catalog.cypher('sample_graph', $$
     MATCH (a:Person)-[r:KNOWS]->(b:Person)
     RETURN a.name, type(r), b.name
 $$) AS (person_a ag_catalog.agtype, rel_type ag_catalog.agtype, person_b ag_catalog.agtype);

**** パス検索 [#p47ef66b]

 SELECT * FROM ag_catalog.cypher('sample_graph', $$
     MATCH path = (a:Person)-[:KNOWS*1..3]->(b:Person)
     WHERE a.name = 'John'
     RETURN path
 $$) AS (path ag_catalog.agtype);

** 循環参照安全性の検証 [#p81ac096]

*** テスト概要 [#obfaebaa]

Neo4jで経験した循環参照によるシステムダウンの問題が、PostgreSQL + AGEでも発生するかを検証しました。

*** パフォーマンステスト結果 [#j8dcc39b]

デカルト積を使った負荷テストを実行:

 # 基本接続とAGE設定
 LOAD 'age';
 SET search_path = ag_catalog, "$user", public;
 
 # 4ノードの組み合わせ(3^4 = 81通り)
 SELECT * FROM ag_catalog.cypher('sample_graph', $$
     MATCH (a), (b), (c), (d)
     RETURN count(*) as total_combinations
 $$) AS (total ag_catalog.agtype);
 
 # 10ノードの組み合わせ(3^10 = 59,049通り)
 SELECT * FROM ag_catalog.cypher('sample_graph', $$
     MATCH (a), (b), (c), (d), (e), (f), (g), (h), (i), (j)
     RETURN count(*) as extreme_combinations
 $$) AS (total ag_catalog.agtype);

*** テスト結果 [#sa54ad58]

|ノード組み合わせ数|結果|実行時間|状態|
|---|---|---|---|
|4ノード (3^4)|81 combinations|正常|✅|
|6ノード (3^6)|729 combinations|正常|✅|
|8ノード (3^8)|6,561 combinations|正常|✅|
|10ノード (3^10)|59,049 combinations|正常|✅|

すべてのテストで**システムダウンは発生せず**、正常に結果が返されました。

*** 安全性の理由 [#qe3ac2d9]

**** PostgreSQLの堅牢なアーキテクチャ [#i301233c]
- 20年以上の実績による安定性
- 成熟したメモリ管理システム
- プロセス分離による障害の局所化

**** リソース制御機能 [#f031bcce]
- work_mem: ワーキングメモリの制限
- statement_timeout: クエリタイムアウトの設定
- max_stack_depth: スタック深度の制限

**** JVMの制約からの解放 [#w10cd27a]
- Neo4jのJVMヒープサイズ制限なし
- より予測可能なメモリ使用パターン

** 循環参照対策のベストプラクティス [#z4f9be51]

*** 1. クエリレベルでの制御 [#rf3b7524]

**** パス長制限の明示的な指定 [#g49e2960]

 SELECT * FROM ag_catalog.cypher('sample_graph', $$
     MATCH path = (a)-[*1..5]->(b)  -- 最大5ホップに制限
     RETURN path
 $$) AS (path ag_catalog.agtype);

**** LIMIT句の使用 [#tacefd67]

 SELECT * FROM ag_catalog.cypher('sample_graph', $$
     MATCH (a)-[*]->(b)
     RETURN a, b
     LIMIT 1000  -- 結果数制限
 $$) AS (node_a ag_catalog.agtype, node_b ag_catalog.agtype);

*** 2. PostgreSQL設定レベルでの制御 [#zf8c2f3f]

 -- セッションレベルでの制限
 SET work_mem = '64MB';  -- ワーキングメモリ制限
 SET statement_timeout = '30s';  -- クエリタイムアウト
 SET max_stack_depth = '2MB';  -- スタック深度制限

*** 3. 循環検出のためのクエリパターン [#w27a7046]

 -- 循環パスの検出(安全バージョン)
 SELECT * FROM ag_catalog.cypher('sample_graph', $$
     MATCH path = (start)-[*1..10]->(start)
     WHERE length(path) > 2
     RETURN path, length(path) as cycle_length
     LIMIT 100
 $$) AS (cycle_path ag_catalog.agtype, length ag_catalog.agtype);

** 監視とトラブルシューティング [#te4390f6]

*** PostgreSQL側の監視 [#h19a676d]

 -- アクティブなクエリの確認
 SELECT pid, query_start, state, query 
 FROM pg_stat_activity 
 WHERE state = 'active' AND query LIKE '%cypher%';
 
 -- メモリ使用量の確認
 SELECT * FROM pg_stat_database WHERE datname = 'agedb';

*** よくあるエラーと解決方法 [#pcfd3723]

**** エラー: function cypher(unknown, unknown) does not exist [#t53bd369]
解決: LOAD 'age'; を実行していない

**** エラー: type "agtype" does not exist [#t7838c8a]
解決: 型指定を ag_catalog.agtype に変更

**** エラー: cannot execute CREATE TABLE in a read-only transaction [#jf8fd4ce]
解決: 書き込み用のトランザクションで実行するか、書き込み権限のあるセッションを使用

** パフォーマンス最適化 [#r83f21f5]

*** インデックスの作成 [#l13aa08c]

 -- プロパティインデックス
 CREATE INDEX idx_person_name 
 ON sample_graph."Person" 
 USING btree ((properties->>'name'));
 
 -- ラベルインデックス
 CREATE INDEX idx_vertex_label 
 ON sample_graph._ag_label_vertex (label);

*** メモリ設定の調整 [#r4110319]

docker-compose.ymlに追加:

 environment:
   POSTGRES_SHARED_BUFFERS: "256MB"
   POSTGRES_WORK_MEM: "64MB"
   POSTGRES_MAINTENANCE_WORK_MEM: "128MB"

** まとめ [#z1f88280]

*** PostgreSQL + AGEの安全性評価: ⭐⭐⭐⭐⭐ (5/5) [#yfaebbbd]

**** 利点 [#a3888d64]
+ **システム安定性**: PostgreSQLの堅牢なアーキテクチャ
+ **リソース制御**: 細かいメモリ・CPU制限が可能
+ **プロセス分離**: 一つのクエリのクラッシュが他に影響しない
+ **監視容易性**: 豊富な統計情報とログ
+ **Neo4j互換性**: Cypherクエリ言語をサポート

**** Neo4jからの移行における安全性向上 [#g3199ca7]
- JVMヒープの制約から解放
- より予測可能なメモリ使用パターン
- 標準的なRDBMS運用ノウハウが活用可能
- 企業環境での運用実績

**** 推奨運用方針 [#hfd8885e]
+. **開発時**: クエリにLIMIT句と深度制限を必ず付与
+. **本番運用**: PostgreSQLの標準的な監視・制限設定を適用
+. **パフォーマンス**: インデックス作成とクエリ最適化
+. **バックアップ**: PostgreSQLの標準的なバックアップ戦略
+ **開発時**: クエリにLIMIT句と深度制限を必ず付与
+ **本番運用**: PostgreSQLの標準的な監視・制限設定を適用
+ **パフォーマンス**: インデックス作成とクエリ最適化
+ **バックアップ**: PostgreSQLの標準的なバックアップ戦略

PostgreSQL + AGEは、Neo4jで経験された循環参照によるシステムダウンの問題を効果的に回避できる、より安全で運用しやすい代替案として強く推奨できます。特に企業環境での本番運用においては、PostgreSQLの実績ある運用ノウハウを活用できることが大きなメリットとなります。

** 参考資料 [#d481ed29]

- [[Apache AGE公式サイト>https://age.apache.org/]]
- [[PostgreSQL公式ドキュメント>https://www.postgresql.org/docs/]]
- [[Model Context Protocol>https://spec.modelcontextprotocol.io/]]
トップ   編集 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS