Neo4jでの循環参照によるシステムダウンを経験した後、より安全で運用しやすいグラフデータベース環境を求めて、PostgreSQL + AGE拡張による代替環境の構築に取り組みました。本記事では、Docker環境での構築から安全性検証まで、実際の手順と検証結果を詳しく紹介します。
project/
├── docker-compose.yml
├── init-scripts/
│ └── init-age.sql
└── examples/
└── age_examples.sql
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:
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();
mkdir postgres-age-project cd postgres-age-project mkdir init-scripts examples # ファイルを配置した後 docker-compose up -d
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)
npm install -g @modelcontextprotocol/server-postgres
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"
}
}
}
}
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を使用する際は、毎回以下の設定が必要です:
LOAD 'age'; SET search_path = ag_catalog, "$user", public;
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);
SELECT * FROM ag_catalog.cypher('sample_graph', $$
MATCH (n:Person)
RETURN n.name, n.age
$$) AS (name ag_catalog.agtype, age ag_catalog.agtype);
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);
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);
Neo4jで経験した循環参照によるシステムダウンの問題が、PostgreSQL + AGEでも発生するかを検証しました。
デカルト積を使った負荷テストを実行:
# 基本接続と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);
| ノード組み合わせ数 | 結果 | 実行時間 | 状態 |
| --- | --- | --- | --- |
| 4ノード (3^4) | 81 combinations | 正常 | ✅ |
| 6ノード (3^6) | 729 combinations | 正常 | ✅ |
| 8ノード (3^8) | 6,561 combinations | 正常 | ✅ |
| 10ノード (3^10) | 59,049 combinations | 正常 | ✅ |
すべてのテストで**システムダウンは発生せず**、正常に結果が返されました。
SELECT * FROM ag_catalog.cypher('sample_graph', $$
MATCH path = (a)-[*1..5]->(b) -- 最大5ホップに制限
RETURN path
$$) AS (path ag_catalog.agtype);
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);
-- セッションレベルでの制限 SET work_mem = '64MB'; -- ワーキングメモリ制限 SET statement_timeout = '30s'; -- クエリタイムアウト SET max_stack_depth = '2MB'; -- スタック深度制限
-- 循環パスの検出(安全バージョン)
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);
-- アクティブなクエリの確認 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';
解決: LOAD 'age'; を実行していない
解決: 型指定を ag_catalog.agtype に変更
解決: 書き込み用のトランザクションで実行するか、書き込み権限のあるセッションを使用
-- プロパティインデックス 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);
docker-compose.ymlに追加:
environment: POSTGRES_SHARED_BUFFERS: "256MB" POSTGRES_WORK_MEM: "64MB" POSTGRES_MAINTENANCE_WORK_MEM: "128MB"
1. **システム安定性**: PostgreSQLの堅牢なアーキテクチャ 2. **リソース制御**: 細かいメモリ・CPU制限が可能 3. **プロセス分離**: 一つのクエリのクラッシュが他に影響しない 4. **監視容易性**: 豊富な統計情報とログ 5. **Neo4j互換性**: Cypherクエリ言語をサポート
1. **開発時**: クエリにLIMIT句と深度制限を必ず付与 2. **本番運用**: PostgreSQLの標準的な監視・制限設定を適用 3. **パフォーマンス**: インデックス作成とクエリ最適化 4. **バックアップ**: PostgreSQLの標準的なバックアップ戦略
PostgreSQL + AGEは、Neo4jで経験された循環参照によるシステムダウンの問題を効果的に回避できる、より安全で運用しやすい代替案として強く推奨できます。特に企業環境での本番運用においては、PostgreSQLの実績ある運用ノウハウを活用できることが大きなメリットとなります。