2026年2月22日日曜日

AI ワークロードのための MySQL + Neo4j:なぜリレーショナルデータベースが今なお重要か

This article was originally published in English at AnotherMySQLDBA.

AIエージェント用の永続メモリを構築する方法を、既にお使いのデータベースを使って文書化する時期が来たと考えました。ベクトルデータベースではありません - MySQLとNeo4jです。

これは理論ではありません。私はこのアーキテクチャを日常的に使用し、複数のプロジェクトでAIエージェントのメモリを管理しています。実際に機能するスキーマとクエリパターンを紹介します。

アーキテクチャ

AIエージェントには2種類のメモリが必要です:

  • 構造化メモリ - 何が起こったか、いつ、なぜ(MySQL)
  • パターンメモリ - 何が何に繋がっているか(Neo4j)

ベクトルデータベースは類似性検索用です。ワークフローの状態や決定履歴を追跡するには適していません。それにはACIDトランザクションと適切なリレーションシップが必要です。

MySQLスキーマ

AIエージェントの永続メモリのための実際のスキーマはこちらです:

-- Architecture decisions the AI made
CREATE TABLE architecture_decisions (
    id INT AUTO_INCREMENT PRIMARY KEY,
    project_id INT NOT NULL,
    title VARCHAR(255) NOT NULL,
    decision TEXT NOT NULL,
    rationale TEXT,
    alternatives_considered TEXT,
    status ENUM('accepted', 'rejected', 'pending') DEFAULT 'accepted',
    decided_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    tags JSON,
    INDEX idx_project_date (project_id, decided_at),
    INDEX idx_status (status)
) ENGINE=InnoDB;

-- Code patterns the AI learned
CREATE TABLE code_patterns (
    id INT AUTO_INCREMENT PRIMARY KEY,
    project_id INT NOT NULL,
    category VARCHAR(50) NOT NULL,
    name VARCHAR(255) NOT NULL,
    description TEXT,
    code_example TEXT,
    language VARCHAR(50),
    confidence_score FLOAT DEFAULT 0.5,
    usage_count INT DEFAULT 0,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME ON UPDATE CURRENT_TIMESTAMP,
    INDEX idx_project_category (project_id, category),
    INDEX idx_confidence (confidence_score)
) ENGINE=InnoDB;

-- Work session tracking
CREATE TABLE work_sessions (
    id INT AUTO_INCREMENT PRIMARY KEY,
    session_id VARCHAR(255) UNIQUE NOT NULL,
    project_id INT NOT NULL,
    started_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    ended_at DATETIME,
    summary TEXT,
    context JSON,
    INDEX idx_project_session (project_id, started_at)
) ENGINE=InnoDB;

-- Pitfalls to avoid (learned from mistakes)
CREATE TABLE pitfalls (
    id INT AUTO_INCREMENT PRIMARY KEY,
    project_id INT NOT NULL,
    category VARCHAR(50),
    title VARCHAR(255) NOT NULL,
    description TEXT,
    how_to_avoid TEXT,
    severity ENUM('critical', 'high', 'medium', 'low'),
    encountered_count INT DEFAULT 1,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    INDEX idx_project_severity (project_id, severity)
) ENGINE=InnoDB;

外部キー。チェック制約。適切なインデックス。これはリレーショナルデータベースが得意とするところです。

クエリパターン

AIエージェントのメモリを実際にクエリする方法はこちらです:

-- Get recent decisions for context
SELECT title, decision, rationale, decided_at
FROM architecture_decisions
WHERE project_id = ?
  AND decided_at > DATE_SUB(NOW(), INTERVAL 30 DAY)
ORDER BY decided_at DESC
LIMIT 10;

-- Find high-confidence patterns
SELECT category, name, description, code_example
FROM code_patterns
WHERE project_id = ?
  AND confidence_score >= 0.80
ORDER BY usage_count DESC, confidence_score DESC
LIMIT 20;

-- Check for known pitfalls before implementing
SELECT title, description, how_to_avoid
FROM pitfalls
WHERE project_id = ?
  AND category = ?
  AND severity IN ('critical', 'high')
ORDER BY encountered_count DESC;

-- Track session context across interactions
SELECT context
FROM work_sessions
WHERE session_id = ?
ORDER BY started_at DESC
LIMIT 1;

これらはストレートフォワードなSQLクエリです。EXPLAINで期待通りのインデックス使用が確認できます。サプライズはありません。

Neo4jレイヤー

MySQLが構造化データを扱い、Neo4jがリレーションシップを扱います:

// Create nodes for decisions
CREATE (d:Decision {
  id: 'dec_123',
  title: 'Use FastAPI',
  project_id: 1,
  embedding: [0.23, -0.45, ...]  // Vector for similarity
})

// Create relationships
CREATE (d1:Decision {id: 'dec_123', title: 'Use FastAPI'})
CREATE (d2:Decision {id: 'dec_45', title: 'Used Flask before'})
CREATE (d1)-[:SIMILAR_TO {score: 0.85}]->(d2)
CREATE (d1)-[:CONTRADICTS]->(d3:Decision {title: 'Avoid frameworks'})

// Query: Find similar past decisions
MATCH (current:Decision {id: $decision_id})
MATCH (current)-[r:SIMILAR_TO]-(similar:Decision)
WHERE r.score > 0.80
RETURN similar.title, r.score
ORDER BY r.score DESC

// Query: What outcomes followed this pattern?
MATCH (d:Decision)-[:LEADS_TO]->(o:Outcome)
WHERE d.title CONTAINS 'Redis'
RETURN d.title, o.type, o.success_rate

それらが連携して動作する仕組み

フローは以下のようになります:

  1. AIエージェントがコンテンツを生成するか決定を下す
  2. MySQLに構造化データを保存(何、いつ、なぜ、完全なコンテキスト)
  3. エンベディングを生成し、類似アイテムとのリレーションシップ付きでNeo4jに保存
  4. 次のセッション:Neo4jが関連する類似決定を見つけ出す
  5. MySQLがその決定の詳細情報を提供

MySQLが真実の源です。Neo4jがパターン発見者です。

なぜベクトルデータベースだけではダメなのか?

PineconeやWeaviateだけでAIエージェントのメモリを構築しようとするチームを見てきましたが、うまく行きません。理由は:

ベクトルDBが得意なこと:

  • クエリに似たドキュメントを見つける
  • セマンティック検索(RAG)
  • "これに似たもの"

ベクトルDBが苦手なこと:

  • "3月15日に何を決定したか?"
  • "障害を引き起こした決定を表示"
  • "このワークフローの現在の状態は?"
  • "confidence > 0.8 AND usage_count > 10のパターンはどれ?"

これらのクエリには構造化フィルタリング、結合、トランザクションが必要です。それはリレーショナルデータベースの領域です。

MCPと未来

Model Context Protocol (MCP)は、AIシステムがコンテキストを扱う方法を標準化しています。初期のMCP実装でも、私たちがすでに知っていたことが明らかになっています:構造化ストレージとグラフリレーションシップの両方が必要です。

``````html

MySQL は MCP の「resources」と「tools」カタログを扱います。Neo4j はコンテキスト項目間の「relationships」を扱います。ベクター埋め込みはパズルの一部に過ぎません。

Production Notes

このアーキテクチャを実行している現在のシステム:

  • MySQL 8.0, 48 tables, ~2GB data
  • Neo4j Community, ~50k nodes, ~200k relationships
  • Query latency: MySQL <10ms, Neo4j <50ms
  • Backup: Standard mysqldump + neo4j-admin dump
  • Monitoring: Same Percona tools I've used for years

運用上の複雑さは低いです。これらは成熟したデータベースで、よく理解された運用パターンを持っています。

When to Use What

Use CaseDatabase
Workflow state, decisions, audit trailMySQL/PostgreSQL
Pattern detection, similarity, relationshipsNeo4j
Semantic document search (RAG)Vector DB (optional)

状態には MySQL から始めましょう。パターン認識が必要になったら Neo4j を追加します。セマンティックドキュメント検索を実際にしている場合にのみ、ベクターデータベースを追加してください。

Summary

AI エージェントには永続的なメモリが必要です。単なるベクターデータベースの埋め込みではなく、構造化された、リレーショナルで、時間的なメモリにパターン認識を備えたものです。

MySQL は構造化された状態を扱います。Neo4j はグラフリレーションシップを扱います。これらを組み合わせることで、ベクターデータベース単独では提供できないものを提供します。

AI ワークロードのためにリレーショナルデータベースを放棄しないでください。それぞれの仕事に適したツールを使用し、それは両方を一緒に使うことです。

このアーキテクチャに関する AI エージェントの視点の詳細については、3k1o の関連投稿をご覧ください。