Mastra で作る AIエージェント(26) Mastra Platform のDBをNeon(PostgreSQL)に切り替える
Mastra で作る AI エージェント というシリーズの第26回です。
前回は、Mastra Platform にプロジェクトをデプロイするところまで試しました。エージェントも動き、Studio も使えるようになり、一通りのことはできています。
しかしここで一つ問題があります。今の構成では、各種ストレージに LibSQL(SQLite)と DuckDB のローカルファイルを使っています。これはローカル開発には便利なのですが、本番環境で使い続けると以下の 2 つの問題が生じます。
- デプロイするたびにデータが初期化される — Mastra Platform はエフェメラルなファイルシステムを使用しているため、デプロイのたびにローカルファイルが消えてしまいます。会話履歴も RAG データも、一切が白紙に戻ります。
- 同時アクセスに弱い — SQLite はファイルロックで排他制御するため、複数ユーザーが同時にアクセスすると競合が起きやすく、本番負荷には向きません。
「DB の永続化」はある意味、Mastra Platform の問題というより本番デプロイ全般に共通する問題です。今回はその解決策として、各種 DB を外部の永続 DB サービスに切り替える手順をご紹介します。外部 DB サービスはいろいろあるのですが、今回は Neon(サーバーレス PostgreSQL) を採用します。
外部 PostgreSQL サービスの選択肢
PostgreSQL 互換の外部サービスとしては、たとえば以下のような選択肢があります。
| サービス | 特徴 |
|---|---|
| Neon | サーバーレス PostgreSQL。コンピュートとストレージを分離した設計で、未使用時はコンピュートが自動停止。pgvector を標準サポート。無料枠あり。 |
| Supabase | PostgreSQL をベースにしたバックエンドプラットフォーム。認証・ストレージ・リアルタイム機能なども含む。フルスタックに便利だが、DB だけ使いたい場合は機能過多になりやすい。 |
| PlanetScale | MySQL 互換のサーバーレス DB。PostgreSQL ではないため @mastra/pg はそのまま使えない。 |
| Render / Railway の Postgres | VPS 系のマネージド PostgreSQL。常時起動型でシンプルだが、無料枠は限定的。 |
| RDS (AWS) / Cloud SQL (GCP) | クラウド大手のマネージド PostgreSQL。本格的な本番環境向けだが、設定の手間とコストが大きい。 |
今回 Neon を採用した理由は主に 3 つです。
-
pgvector を標準サポートしている — RAG のベクトルストア(
PgVector)の移行先として、一つの DB で ストレージと ベクトルデータの両方を賄えます -
@mastra/pgが Neon と相性よく動く — Mastra の公式パッケージが PostgreSQL を前提に設計されており、Neon の接続文字列をそのまま渡すだけで動作します - 無料枠で試しやすい — 今回のような実験・小規模本番には無料プランで十分です。コンピュートが自動停止するためコストも抑えられます
BEFORE: libsql・DuckDB を使っている
まず、切り替え前の状態を整理しておきましょう。各種ストレージの役割分担はこうなっています。
| 役割 | 変更前 |
|---|---|
| 会話履歴・ワークフロー・メトリクス |
LibSQLStore(file:./mastra.db) |
| Observability(Logs・Traces) |
ObservabilityStorageDuckDB(mastra.duckdb) |
| RAG ベクトルデータ |
LibSQLVector(file:./rag-data/mastra-vectors.db) |
src/mastra/index.ts(変更前)
import { Mastra } from '@mastra/core/mastra';
import { SimpleAuth } from '@mastra/core/server';
import { PinoLogger } from '@mastra/loggers';
import { LibSQLStore } from '@mastra/libsql';
import { DuckDBConnection, ObservabilityStorageDuckDB } from '@mastra/duckdb';
import { MastraCompositeStore } from '@mastra/core/storage';
import { Observability, DefaultExporter, CloudExporter, SensitiveDataFilter } from '@mastra/observability';
import { weatherWorkflow } from './workflows/weather-workflow';
import { weatherAgent } from './agents/weather-agent';
import { ragAgent } from './agents/rag-agent';
import { ragVector } from './vectors/rag-vector';
// ...(スコアラーの import 省略)
export const mastra = new Mastra({
server: {
auth: new SimpleAuth({
tokens: {
[process.env.API_TOKEN!]: { id: 'user-1', name: 'Admin', role: 'admin' },
},
}),
},
workflows: { weatherWorkflow },
agents: { weatherAgent, ragAgent },
vectors: { ragVector },
// ...(スコアラー省略)
storage: new MastraCompositeStore({
id: 'composite-storage',
default: new LibSQLStore({
id: 'mastra-storage',
url: 'file:./mastra.db', // ← ローカルファイル
}),
domains: {
observability: new ObservabilityStorageDuckDB({
db: new DuckDBConnection({ path: 'mastra.duckdb' }), // ← ローカルファイル
}),
},
}),
logger: new PinoLogger({ name: 'Mastra', level: 'info' }),
observability: new Observability({ /* ... */ }),
});
会話履歴は LibSQLStore(mastra.db)に、Observability(Logs・Traces)は ObservabilityStorageDuckDB(mastra.duckdb)に分けて管理しているため、MastraCompositeStore で 2 つを束ねています。
src/mastra/vectors/rag-vector.ts(変更前)
import { LibSQLVector } from '@mastra/libsql';
import * as fs from 'fs';
import * as path from 'path';
const VECTOR_DB_URL = process.env.VECTOR_DB_URL
?? `file:${path.join(process.cwd(), 'rag-data', 'mastra-vectors.db')}`;
if (VECTOR_DB_URL.startsWith('file:')) {
const filePath = VECTOR_DB_URL.replace(/^file:/, '');
fs.mkdirSync(path.dirname(filePath), { recursive: true });
}
export const ragVector = new LibSQLVector({
id: 'rag-vector',
url: VECTOR_DB_URL,
authToken: process.env.VECTOR_DB_AUTH_TOKEN,
});
export const RAG_INDEX_NAME = 'rag_index';
export const EMBEDDING_DIMENSION = 1536;
ローカル開発時はファイルパスを自動組み立てし、Platform デプロイ時は VECTOR_DB_URL 環境変数で LibSQL 互換の外部サービス(Turso など)に切り替える設計でした。
BEFORE の状態を Platform で動かすと…
エージェントの呼び出しも Studio での操作も一見正常に動いています。

エージェント呼び出しは正常に動く
メトリクス(スコアラー結果)も記録されています。

スコアラー一覧

スコアラー詳細
Traces と Logs もきちんと記録されています。

Traces

Logs
これだけ見ると「問題なく動いている」ように見えてしまいますが、再デプロイすると全てのデータが消えます。libsql・DuckDB が書き込んでいるのはコンテナ内のローカルファイルであり、デプロイのたびに新しいコンテナが起動してデータは消滅します。本番運用では完全に使い物になりません。
AFTER: Neon(PostgreSQL)に切り替える
切り替え後の構成はこうなります。
| 役割 | 変更後 |
|---|---|
| 会話履歴・ワークフロー |
PostgresStore(Neon) |
| Observability(Logs・Traces) |
PostgresStore(Neon・同一DB) |
| RAG ベクトルデータ |
PgVector(Neon with pgvector) |
@mastra/pg の PostgresStore はストレージと Observability の両方を 1 つで担えるため、MastraCompositeStore が不要になります。PostgresStore と PgVector は同じ Neon DB 内の別テーブルを使用するため、接続文字列を共有しても問題ありません。
Metrics(スコアラー結果)について: 現時点で Mastra の
PostgresStoreは Metrics(スコアラー結果)の永続化に対応していません。そのため、今回の移行対象には含めていません。Platform 上でも Metrics を永続化したい場合は、ClickHouse を別途用意してMastraCompositeStoreでストレージと組み合わせる構成が必要です。今回のブログ記事では「Metrics なし・そのほかは永続化」で割り切っています。
Step 1. Neon のセットアップ
アカウント作成
まず Neon の公式サイト にアクセスし、「Sign Up」からアカウントを作成します。GitHub アカウントで登録するのが一番スムーズです。

サインアップ画面。GitHub、Google、メールアドレスのいずれかで登録できます
GitHub 認証を選択します。

認証が完了すると、Neon の管理画面に入ります。


プロジェクト作成
プロジェクト名とリージョンを設定します。 レイテンシを抑えるために AWS Tokyo (ap-northeast-1) を選びたいところですが、選択肢にないので シンガポール を選びます。

プロジェクト名とリージョンを設定する
「Create Project」をクリックするとわずか数秒で DB が立ち上がります。これがサーバーレス PostgreSQL の強みです。
接続文字列のコピー
プロジェクト作成後に表示される Connection string をコピーしておきます。この文字列が設定の核になります。

postgresql://username:password@ep-xxx-xxx.ap-northeast-1.aws.neon.tech/dbname?sslmode=require
Neon ダッシュボードはこのような見た目です。SQL エディタも内蔵されており、ブラウザから直接 SQL を実行できます。

接続確認
SQL エディタで SELECT 1; を実行して接続を確認します。

pgvector 拡張の有効化
RAG のベクトルストアに使うため、pgvector 拡張を有効化します。Neon は標準でサポートしていますが、念のため SQL コンソールで実行しておきましょう。
CREATE EXTENSION IF NOT EXISTS vector;

テーブルの手動作成は不要です。
PostgresStoreはサーバー起動時に、PgVectorはcreateIndex()呼び出し時に、それぞれ必要なテーブルを自動生成します。
Step 2. パッケージのインストール
pnpm add @mastra/pg
不要になった LibSQL・DuckDB パッケージは後で削除できます(他で参照していないことを確認してから)。
pnpm remove @mastra/libsql @mastra/duckdb
Step 3. 環境変数の設定
ローカル(.env)
.env に DATABASE_URL を追加します。VECTOR_DB_URL / VECTOR_DB_AUTH_TOKEN は不要になります。
# Neon 接続文字列(ストレージ・Observability・ベクトルストアで共有)
DATABASE_URL=postgresql://username:password@ep-xxx-xxx.ap-northeast-1.aws.neon.tech/dbname?sslmode=require
# 以下は削除 or コメントアウト
# VECTOR_DB_URL=...
# VECTOR_DB_AUTH_TOKEN=...
Mastra Platform ダッシュボード
Platform 側の環境変数も更新が必要です。DATABASE_URL を追加し、VECTOR_DB_URL / VECTOR_DB_AUTH_TOKEN が設定済みの場合は削除します。
- projects.mastra.ai を開く
- プロジェクト → Server → Environment Variables
-
DATABASE_URLに Neon の接続文字列を設定

Platform の Environment Variables 画面で DATABASE_URL を設定する
Step 4. src/mastra/index.ts の修正
MastraCompositeStore を廃止し、PostgresStore 一本に集約します。DATABASE_URL が設定されていれば Neon を、なければ LibSQL にフォールバックする形にすることで、ローカル開発時は今まで通り動かせます。
import { Mastra } from '@mastra/core/mastra';
import { SimpleAuth } from '@mastra/core/server';
import { PinoLogger } from '@mastra/loggers';
- import { LibSQLStore } from '@mastra/libsql';
- import { DuckDBConnection, ObservabilityStorageDuckDB } from '@mastra/duckdb';
- import { MastraCompositeStore } from '@mastra/core/storage';
+ import { PostgresStore } from '@mastra/pg';
+ import { LibSQLStore } from '@mastra/libsql';
import { Observability, DefaultExporter, CloudExporter, SensitiveDataFilter } from '@mastra/observability';
// ...(他の import は変更なし)
export const mastra = new Mastra({
// ...(server, workflows, agents, vectors, scorers は変更なし)
- storage: new MastraCompositeStore({
- id: 'composite-storage',
- default: new LibSQLStore({
- id: 'mastra-storage',
- url: 'file:./mastra.db',
- }),
- domains: {
- observability: new ObservabilityStorageDuckDB({
- db: new DuckDBConnection({ path: 'mastra.duckdb' }),
- }),
- },
- }),
+ storage: process.env.DATABASE_URL
+ ? new PostgresStore({
+ id: 'mastra-storage',
+ connectionString: process.env.DATABASE_URL,
+ ssl: { rejectUnauthorized: false },
+ })
+ : new LibSQLStore({ id: 'mastra-storage', url: 'file:./mastra.db' }),
logger: new PinoLogger({ name: 'Mastra', level: 'info' }),
observability: new Observability({ /* ... */ }),
});
PostgresStore は Observability(Logs・Traces)も自動的に担うため、ObservabilityStorageDuckDB と MastraCompositeStore は不要になります。
Step 5. src/mastra/vectors/rag-vector.ts の修正
LibSQLVector を PgVector に差し替えます。DATABASE_URL がある場合は Neon を、ない場合は LibSQL にフォールバックします。RAG_INDEX_NAME と EMBEDDING_DIMENSION の定数はそのままです。
- import { LibSQLVector } from '@mastra/libsql';
- import * as fs from 'fs';
- import * as path from 'path';
+ import { PgVector } from '@mastra/pg';
+ import { LibSQLVector } from '@mastra/libsql';
- const VECTOR_DB_URL = process.env.VECTOR_DB_URL
- ?? `file:${path.join(process.cwd(), 'rag-data', 'mastra-vectors.db')}`;
-
- if (VECTOR_DB_URL.startsWith('file:')) {
- const filePath = VECTOR_DB_URL.replace(/^file:/, '');
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
- }
-
- export const ragVector = new LibSQLVector({
- id: 'rag-vector',
- url: VECTOR_DB_URL,
- authToken: process.env.VECTOR_DB_AUTH_TOKEN,
- });
+ export const ragVector = process.env.DATABASE_URL
+ ? new PgVector({
+ id: 'rag-vector',
+ connectionString: process.env.DATABASE_URL,
+ ssl: { rejectUnauthorized: false },
+ })
+ : new LibSQLVector({ id: 'rag-vector', url: 'file:./rag-data/mastra-vectors.db' });
export const RAG_INDEX_NAME = 'rag_index';
export const EMBEDDING_DIMENSION = 1536;
Step 6. RAG データの再 ingest
ベクトルストアが新しい Neon DB に変わったため、既存の埋め込みデータを再投入する必要があります。
npm run ingest
Step 7. ビルド確認と Platform への再デプロイ
ローカルでビルドエラーがないことを確認したら、Platform へデプロイします。
npm run build
mastra server deploy
Platform ダッシュボードで DATABASE_URL が設定済みであることを確認してからデプロイしてください。
切り替え後の動作確認
再デプロイ後、エージェントを呼び出してみます。

エージェントが正常に動いています
Metrics 画面を確認します。スコアラーの結果がきちんと記録されています。

Metrics(スコアラー結果)
Traces も正常に記録されています。

Traces
Logs も問題なく記録されています。

Logs
今度は再デプロイしてもデータが消えません。Neon の DB にデータが永続化されているからです。
まとめ
今回の変更のポイントをまとめます。
-
MastraCompositeStoreを廃止 —PostgresStoreがストレージと Observability の両方を担えるため、2 つの DB で管理する必要がなくなります -
接続文字列の共有 —
PostgresStoreとPgVectorは同じ Neon DB 内の別テーブルを使うため、DATABASE_URL1 つで管理できます -
テーブルの自動生成 —
PostgresStoreは起動時に、PgVectorはcreateIndex()時に必要なテーブルを自動作成するため、マイグレーション作業は不要です -
フォールバック設計 —
DATABASE_URLがなければ LibSQL にフォールバックするようにしておくと、ローカル開発環境への影響を最小限に抑えられます -
RAG データの再 ingest が必要 — ベクトルストアが変わるため、移行後は必ず
npm run ingestを実行してください
本番環境では今回のように外部の永続 DB を使うことが前提です。Neon は無料プランでも十分使えるため、まずは試してみてください。
次回は Mastra Platform でWorkspaces機能を使ってみます。
Discussion