AIエージェントのDB設計 — MastraとLangGraph.jsにおける永続化パターン

Maru

@maru

AI 에이전트 DB 설계 — Mastra와 LangGraph.js의 영속화 패턴

AIエージェントのDB設計 — MastraとLangGraph.jsにおける永続化パターン

TypeScriptベースのAIエージェントをプロダクション環境にデプロイする際、最初に直面する壁が「状態の永続化」です。単に会話記録をサーバーメモリに一時保存する段階を超え、今や複雑なワークフローのスナップショットやトランザクションまでを安定して保持できる多層データベース設計が不可欠です。Mastra、LangGraph.js、Vercel AI SDKといったツールが、分散環境下での状態汚染やコネクションのボトルネックを解消するために採用している、プロダクションレベルのDB設計パターンと実装戦略を解説します。

Mastraの分割設計:MastraCompositeStore

Mastra 1.0では、単一データベース設計から脱却し、データドメインごとにストレージを組み合わせて利用する「MastraCompositeStore」アーキテクチャが正式導入されました。この構造を活用することで、サービスの特性に合わせて各状態ドメインごとに最適なデータベースを分離して管理することが可能になります。

最も代表的なのが、会話セッションとワークフローエンジンの分離です。高速なレスポンスが求められる短期的な会話セッションやワークメモリ領域である memoryドメインは、エッジ環境に適した MemoryLibSQLへと割り当てて遅延を最小化します。一方で、実行ノードの追跡や中断後の再開、リトライ制御などが重要で、障害リスクを抑える必要があるオーケストレーション状態の workflowsドメインは、トランザクションの信頼性が高いPostgreSQLバックエンドである WorkflowsPGへ送信し、永続化します。

Mastra 1.0+に基づく、代表的な二層永続化設定の例です。

typescript
import { Mastra } from '@mastra/core';
import { MastraCompositeStore } from '@mastra/core/storage';
import { MemoryLibSQL } from '@mastra/libsql';
import { WorkflowsPG } from '@mastra/pg';

export const mastra = new Mastra({
  storage: new MastraCompositeStore({
    id: 'composite-storage',
    domains: {
      memory: new MemoryLibSQL({ url: 'file:./memory.db' }),
      workflows: new WorkflowsPG({ connectionString: process.env.DATABASE_URL }),
    },
  }),
});

このような分割永続化手法は、単一データベースを共有する際に発生するパフォーマンスのボトルネックを防ぎます。エッジなどで軽量に動作するエージェントの会話ループはローカルや近接ノードでほぼリアルタイムに記録しつつ、安全な状態トランザクションが求められるビジネスワークフローの状態はメインデータベースへ確実に保存するという、柔軟なアーキテクチャを実現できます。

LangGraph.jsの2段階メモリ分離とDBコネクションのチューニング

LangGraph.jsは、エージェントの状態永続化を短期と長期に明確に分離することで、永続化レイヤーの負荷を制御します。単一スレッド内の詳細なワークフローや状態スナップショットを密に記録する「チェックポインター(Checkpointer)」と、複数のスレッドで会話の文脈を共有する「グローバルストア(Store)」という二重構造になっています。このパターンをPostgreSQL環境でプロダクションレベルで実装する際には、@langchain/langgraph-checkpoint-postgres パッケージの PostgresSaverを使用します。

しかし、このパターン導入時に「コネクションリーク」と「トランザクション失敗」という問題に直面しがちです。サーバーの再起動やホットリロードのたびに不要なコネクションが重複して蓄積されないよう、コネクションプールは必ずグローバルなシングルトンインスタンスとして管理する必要があります。また、ドライバーおよびセーバーの設定時には autocommit: true オプションを有効化しなければなりません。このオプションが漏れると、データベーススキーマの生成やスナップショットデータのコミットが待機状態に陥ったり、トランザクションが恒久的に消失したりする可能性があります。

シングルトン・コネクションプールと autocommit オプションを適用して、永続化レイヤーを安定的に初期化する実装例です。

typescript
import { PostgresSaver } from "@langchain/langgraph-checkpoint-postgres";
import pg from "pg";

// 글로벌 싱글톤 패턴으로 커넥션 풀을 관리해 핫 리로드 시 누수를 방지합니다.
const pool = globalThis.dbPool || new pg.Pool({
  connectionString: process.env.DATABASE_URL,
  max: 10,
});

if (process.env.NODE_ENV !== "production") {
  globalThis.dbPool = pool;
}

// autocommit 설정을 활성화하여 체크포인트 스냅샷 커밋 실패를 예방합니다.
const checkpointer = new PostgresSaver(pool, {
  autocommit: true,
});

セキュリティの観点からも、マルチテナント環境において信頼できないペイロードによるMsgPackデシリアライズ攻撃を未然に防ぐため、厳格な分離および検証レイヤーを構築するのが安全です。チェックポインターとグローバルストア間で一つのシングルトン・コネクションプールを密接に共有しつつも、ドライバーの詳細設定を正確にチューニングしてこそ、実運用環境でのメモリ整合性を確保できます。

Vercel AI SDKにおけるストリーム切断対策とDB同期

LLMとツールを連携させた対話型エージェントの提供において、最も頻発する障害は、ストリーム送信中にユーザーがブラウザタブを閉じたりネットワークが遮断されたりする状況です。この場合、クライアント側で接続を切断するとサーバー側のストリーム送信も即座に中断されます。問題は、この過程でエージェントのツール実行ループが途中で途切れたり、永続化処理を行う onFinish コールバックが呼び出されなかったりすることで、データベースの状態が不整合のまま残ってしまう点です。

Vercel AI SDKでは、こうしたストリーム中断による状態汚染を防ぐため、consumeStream() メソッドが提供されています。クライアントとの接続が切断されても、サーバー内部で残りのストリームデータを最後まで強制的に消費させることで、エージェントのすべてのツール呼び出しループや永続化処理が安全に完了することを保証します。

typescript
import { streamText } from 'ai';
import { openai } from '@ai-sdk/openai';

const result = streamText({
  model: openai('gpt-4o'),
  prompt: '...',
  onFinish: async ({ text }) => {
    // 클라이언트 중단 여부와 무관하게 반드시 실행되어야 하는 DB 저장 로직
    await saveToDatabase(text);
  }
});

// 클라이언트 연결이 유실되어도 서버에서 스트림을 완수하여 onFinish를 실행합니다.
result.consumeStream();

これに加えて、エージェントの動作メモリと会話履歴をより柔軟に管理するために、@ai-sdk-tools/memory パッケージを組み合わせて利用するパターンが推奨されます。このパッケージが提供する DrizzleProviderUpstashProvider を活用すれば、複雑なデータベース更新フックを手動で書く必要なく、エージェントの状態変化をDrizzle ORMやRedisへ自動的に同期できるため、アーキテクチャのトランザクション安全性が一段と強化されます。

プロダクションにおけるエージェントDB選定基準

プロダクション環境のAIエージェントDB設計は、単なる会話記録の保存レベルを超え、システムの完結性と非同期フローを確実に保証する段階へ進化させるべきです。フレームワークの選定や永続化アーキテクチャは、エージェントが扱う作業の特性に合わせて慎重に決定する必要があります。

短期的な会話のリアルタイムなレスポンスと、大規模な実行グラフの弾力性が同時に必要であれば、Mastraによる分割設計が効果的です。セッションの文脈と短期メモリはエッジフレンドリーで軽量なlibSQLへ、複雑なワークフローの状態はトランザクション処理が確実なPostgreSQLへと分離して保存することで、インフラ負荷を適切に分散できます。

精緻な状態制御やグラフ追跡機能が重要であれば、LangGraph.jsモデルが有利です。この手法を採用する際は、コネクション枯渇を防ぐためのグローバルConnectionPoolシングルトンパターンと autocommit: true オプションをインフラ階層に必ず事前に反映させておく必要があります。一方で、ユーザー体験を最大化するストリーミング連携が最優先であれば、Vercel AI SDKの検討をお勧めします。ユーザーがブラウザを閉じてもエージェントの残りの処理や最終状態が安全にコミットされるよう、consumeStream を連動させたバックエンド同期設計を実装しておくことが、障害のないプロダクションサービス構築には不可欠です。


参考リンク