目次

はじめに

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

なぜPostgreSQL + AGEなのか

Neo4jの課題

PostgreSQL + AGEの利点

環境構築

必要なファイル構造

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

Docker Compose設定

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拡張初期化スクリプト

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)

MCP (Model Context Protocol) 接続設定

前提条件

npm install -g @modelcontextprotocol/server-postgres

Claude Desktop設定

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設定

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使用時の重要なポイント

基本設定(毎回必須)

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正常

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

安全性の理由

* PostgreSQLの堅牢なアーキテクチャ

* リソース制御機能

* JVMの制約からの解放

循環参照対策のベストプラクティス

1. クエリレベルでの制御

* パス長制限の明示的な指定

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

* LIMIT句の使用

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設定レベルでの制御

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

3. 循環検出のためのクエリパターン

-- 循環パスの検出(安全バージョン)
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);

監視とトラブルシューティング

PostgreSQL側の監視

-- アクティブなクエリの確認
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';

よくあるエラーと解決方法

* エラー: function cypher(unknown, unknown) does not exist

解決: LOAD 'age'; を実行していない

* エラー: type "agtype" does not exist

解決: 型指定を ag_catalog.agtype に変更

* エラー: cannot execute CREATE TABLE in a read-only transaction

解決: 書き込み用のトランザクションで実行するか、書き込み権限のあるセッションを使用

パフォーマンス最適化

インデックスの作成

-- プロパティインデックス
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"

まとめ

PostgreSQL + AGEの安全性評価: ⭐⭐⭐⭐⭐ (5/5)

* 利点

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

* Neo4jからの移行における安全性向上

* 推奨運用方針

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

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

参考資料

トップ   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS