跳至主要內容

儲存

儲存 API 已標準化,所有方法均採用一致的分頁和命名模式。

資料庫遷移
資料庫遷移 的直接連結

請透過你平常的遷移流程(例如 Prisma Migrate、Drizzle Kit 或 DBA 審核流程)執行以下 SQL 遷移。

重新命名 Scorers 資料表欄位
重新命名 Scorers 資料表欄位 的直接連結

mastra_scorers 中的 runtimeContext 欄位已重新命名為 requestContext

誰需要執行此遷移

只有在你將 @mastra/pg@mastra/libsql 配合 Evals/評分使用,而且 runtimeContext 欄位中已有資料時才需要。

不執行會導致甚麼問題

現有評分記錄的請求內容資料將無法存取。

部署 v1(初始化時會加入新的 requestContext 欄位)後,複製資料並移除舊欄位:

UPDATE mastra_scorers SET "requestContext" = "runtimeContext" WHERE "runtimeContext" IS NOT NULL;
ALTER TABLE mastra_scorers DROP COLUMN "runtimeContext";

重複 span 遷移
重複 span 遷移 的直接連結

如果你從較舊版本的 Mastra 升級,mastra_spans 資料表中可能有重複的 (traceId, spanId) 項目。v1 會為這些欄位加入唯一約束以確保資料完整性,但如果存在重複項目,就無法加入此約束。

誰需要執行此遷移

只有在你有來自 v1 之前 Mastra 版本的現有 span 資料,並遇到重複鍵違規或建立約束失敗的錯誤時才需要。

不執行會導致甚麼問題

儲存初始化可能會在嘗試加入唯一約束時失敗,或你可能會看到「duplicate key value violates unique constraint」之類的錯誤。

執行遷移命令,它會自動移除重複的 span 並加入約束:

npx mastra migrate

CLI 會先打包你的項目並連接至已設定的儲存,然後執行遷移。移除重複項目時,它會保留最完整的記錄(根據 endTime 和 attributes 判斷)。

選項 2:手動執行 SQL(PostgreSQL)
選項 2:手動執行 SQL(PostgreSQL) 的直接連結

如果你想手動執行遷移:

-- Remove duplicates, keeping the most complete record
DELETE FROM mastra_spans a USING mastra_spans b
WHERE a.ctid < b.ctid
AND a."traceId" = b."traceId"
AND a."spanId" = b."spanId";

-- Add the unique constraint
ALTER TABLE mastra_spans ADD CONSTRAINT mastra_spans_trace_span_unique UNIQUE ("traceId", "spanId");

選項 3:手動遷移(其他資料庫)
選項 3:手動遷移(其他資料庫) 的直接連結

如使用 ClickHouse、LibSQL、MongoDB 或 MSSQL,請使用編程 API:

const storage = mastra.getStorage()
const observabilityStore = await storage.getStore('observability')

// Check if migration is needed
const status = await observabilityStore?.checkSpansMigrationStatus()
console.log(status)

// Run the migration
const result = await observabilityStore?.migrateSpans()
console.log(result)

JSON 欄位(TEXT → JSONB)
JSON 欄位(TEXT → JSONB) 的直接連結

僅限 PostgreSQL。mastra_threads 中的 metadata 欄位和 mastra_workflow_snapshot 中的 snapshot 欄位已由 TEXT 改為 JSONB。

建議

遷移至 JSONB 後,可使用 PostgreSQL 原生 JSON 運算子和 GIN 索引,提升 JSON 欄位的查詢效能。

ALTER TABLE mastra_threads
ALTER COLUMN metadata TYPE jsonb
USING metadata::jsonb;

ALTER TABLE mastra_workflow_snapshot
ALTER COLUMN snapshot TYPE jsonb
USING snapshot::jsonb;

新增
新增 的直接連結

MastraCompositeStore 中組合儲存
storage-composition-in-mastracompositestore 的直接連結

MastraCompositeStore 現在可以組合來自不同 adapter 的儲存 domain。當你需要針對不同用途使用不同資料庫時,可以使用它——例如以 PostgreSQL 儲存記憶和 Workflow,並以專門的資料庫處理 observability。

import { MastraCompositeStore } from '@mastra/core/storage'
import { MemoryPG, WorkflowsPG, ScoresPG } from '@mastra/pg'
import { MemoryLibSQL } from '@mastra/libsql'
import { Mastra } from '@mastra/core'

// Compose domains from different stores
const mastra = new Mastra({
storage: new MastraCompositeStore({
id: 'composite',
domains: {
memory: new MemoryLibSQL({ url: 'file:./local.db' }),
workflows: new WorkflowsPG({ connectionString: process.env.DATABASE_URL }),
scores: new ScoresPG({ connectionString: process.env.DATABASE_URL }),
},
}),
})

詳情請參閱儲存組合參考資料

已變更
已變更 的直接連結

MastraStorage 重新命名為 MastraCompositeStore
mastrastorage-renamed-to-mastracompositestore 的直接連結

MastraStorage 類別已重新命名為 MastraCompositeStore,以更準確反映它作為複合儲存實作的角色:將不同 domain 路由至不同的底層 store。這可避免它與一般的「Mastra Storage」概念(即 Mastra instance 上的 storage 屬性)混淆。

為向後兼容,舊名稱 MastraStorage 仍可作為已棄用的 alias 使用,但將於未來版本移除。

要進行遷移,請更新 import 和 instance 化方式:

- import { MastraStorage } from "@mastra/core/storage";
+ import { MastraCompositeStore } from "@mastra/core/storage";
import { MemoryLibSQL } from "@mastra/libsql";
import { WorkflowsPG } from "@mastra/pg";

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

如果你直接使用單一 store 實作(例如 PostgresStoreLibSQLStore),請保留現有設定。此變更只影響明確使用 MastraStorage 組合儲存的程式碼。

儲存 instance 必須提供 id 屬性
required-id-property-for-storage-instances 的直接連結

儲存 instance 現在必須提供 id 屬性。這個唯一識別碼用於在 Mastra 中追蹤和管理儲存 instance。應為應用程式中的每個儲存 instance 設定一個具描述性且唯一的 id 字串。

要進行遷移,請在儲存 constructor 中加入 id 欄位。

const storage = new PostgresStore({
+ id: 'main-postgres-store',
connectionString: process.env.POSTGRES_CONNECTION_STRING,
schemaName: 'public',
});

const upstashStore = new UpstashStore({
+ id: 'upstash-cache-store',
url: process.env.UPSTASH_REDIS_REST_URL,
token: process.env.UPSTASH_REDIS_REST_TOKEN,
});

分頁由 offset/limit 改為 page/perPage
pagination-from-offsetlimit-to-pageperpage 的直接連結

所有分頁 API 現在都使用 pageperPage,而非 offsetlimit,以配合以頁面為基礎的網絡分頁方式。

要進行遷移,請將所有分頁參數由 offset/limit 更新為 page/perPage。請注意,page 的索引由 0 開始。

memoryStore.listMessages({
threadId: 'thread-123',
- offset: 0,
- limit: 20,
+ page: 0,
+ perPage: 20,
});

getMessagesPaginated 改為 listMessages
getmessagespaginated-to-listmessages 的直接連結

getMessagesPaginated() 方法已由 listMessages() 取代。新方法支援以 perPage: false 擷取所有記錄而不分頁。此變更符合 list* 命名慣例,亦讓擷取所有記錄的方式更靈活。

要進行遷移,請重新命名方法並更新分頁參數。你現在可以使用 perPage: false 擷取所有記錄。

+ const memoryStore = await storage.getStore('memory');
+
// Paginated
- const result = await storage.getMessagesPaginated({
+ const result = await memoryStore?.listMessages({
threadId: 'thread-123',
- offset: 0,
- limit: 20,
+ page: 0,
+ perPage: 20,
});

// Fetch all records (no pagination limit)
+ const allMessages = await memoryStore?.listMessages({
+ threadId: 'thread-123',
+ page: 0,
+ perPage: false,
+ });
Codemod

你可以使用 Mastra 的 codemod CLI 自動更新程式碼:

npx @mastra/codemod@latest v1/storage-get-messages-paginated .

透過 getStore() 存取指定 domain 的儲存
domain-specific-storage-access-via-getstore 的直接連結

儲存操作現在要透過指定 domain 的 store 存取,而非直接在儲存 instance 上存取。

domain 包括:

  • memory - thread、訊息和資源
  • workflows - Workflow snapshot
  • scores - 評估分數
  • observability - Trace 和 span
  • agents - 已儲存的 Agent 資料

要進行遷移,請以 domain 名稱呼叫 getStore(),然後在傳回的 store 上呼叫方法。

const storage = mastra.getStorage();

// Memory operations (threads, messages, resources)
- const thread = await storage.getThread({ threadId: '123' });
- await storage.saveThread({ thread });
+ const memoryStore = await storage.getStore('memory');
+ const thread = await memoryStore?.getThreadById({ threadId: '123' });
+ await memoryStore?.saveThread({ thread });

// Workflow operations (snapshots)
- const snapshot = await storage.loadWorkflowSnapshot({ runId, workflowName });
- await storage.persistWorkflowSnapshot({ runId, workflowName, snapshot });
+ const workflowStore = await storage.getStore('workflows');
+ const snapshot = await workflowStore?.loadWorkflowSnapshot({ runId, workflowName });
+ await workflowStore?.persistWorkflowSnapshot({ runId, workflowName, snapshot });

// Observability operations (traces, spans)
- const traces = await storage.listTraces({ page: 0, perPage: 20 });
+ const observabilityStore = await storage.getStore('observability');
+ const traces = await observabilityStore?.listTraces({ page: 0, perPage: 20 });

// Score operations (evaluations)
- const scores = await storage.listScoresByScorerId({ scorerId: 'helpfulness' });
+ const scoresStore = await storage.getStore('scores');
+ const scores = await scoresStore?.listScoresByScorerId({ scorerId: 'helpfulness' });

getThreadsByResourceId 改為 listThreads
getthreadsbyresourceid-to-listthreads 的直接連結

getThreadsByResourceId() 方法已由 listThreads() 取代。新方法加入分頁支援,亦可按 resourceIdmetadata 或同時按兩者篩選。

important

舊有的 getThreadsByResourceId() 會傳回所有符合條件的 thread,而不會分頁。新的 listThreads() 必須提供分頁參數。要保留擷取所有 thread 的舊有行為,請使用 perPage: false

要進行遷移,請使用 memory store 和新的 listThreads() 方法,並提供分頁參數及可選的 filter object。

- const threads = await storage.getThreadsByResourceId({
- resourceId: 'res-123',
- });
+ const memoryStore = await storage.getStore('memory');
+
+ // Paginated (recommended for large datasets)
+ const result = await memoryStore?.listThreads({
+ filter: { resourceId: 'res-123' },
+ page: 0,
+ perPage: 20,
+ });
+ const threads = result?.threads;
+
+ // Or fetch all threads like before (use perPage: false)
+ const allResult = await memoryStore?.listThreads({
+ filter: { resourceId: 'res-123' },
+ perPage: false,
+ });
+ const allThreads = allResult?.threads;

新方法亦支援:

  • 列出所有 thread(省略 filter)
  • 只按 metadata 篩選
  • 結合 resourceId + metadata 篩選
// List all threads
await memoryStore?.listThreads({ page: 0, perPage: 20 })

// Filter by metadata only
await memoryStore?.listThreads({
filter: { metadata: { status: 'active' } },
page: 0,
perPage: 20,
})

// Combined filter
await memoryStore?.listThreads({
filter: {
resourceId: 'user-123',
metadata: { category: 'support' },
},
page: 0,
perPage: 20,
})
Codemod

你可以使用 Mastra 的 codemod CLI 自動更新程式碼:

npx @mastra/codemod@latest v1/storage-list-threads-by-resource-to-list-threads .

getWorkflowRuns 改為 listWorkflowRuns
getworkflowruns-to-listworkflowruns 的直接連結

getWorkflowRuns() 方法已重新命名為 listWorkflowRuns()。此變更符合以 list* 方法傳回集合的慣例。

要進行遷移,請使用 workflows store、重新命名方法呼叫,並更新分頁參數。

- const runs = await storage.getWorkflowRuns({
+ const workflowStore = await storage.getStore('workflows');
+ const runs = await workflowStore?.listWorkflowRuns({
fromDate,
toDate,
+ page: 0,
+ perPage: 20,
});
Codemod

你可以使用 Mastra 的 codemod CLI 自動更新程式碼:

npx @mastra/codemod@latest v1/storage-list-workflow-runs .

getMessagesById 改為 listMessagesById
getmessagesbyid-to-listmessagesbyid 的直接連結

getMessagesById() 方法已重新命名為 listMessagesById()。此變更符合以 list* 方法傳回集合的慣例。

要進行遷移,請使用 memory store 並重新命名方法呼叫。

+ const memoryStore = await storage.getStore('memory');
- const result = await storage.getMessagesById({
+ const result = await memoryStore?.listMessagesById({
messageIds: ['msg-1', 'msg-2'],
});
Codemod

你可以使用 Mastra 的 codemod CLI 自動更新程式碼:

npx @mastra/codemod@latest v1/storage-list-messages-by-id .

儲存 getMessagessaveMessages 的 signature
storage-getmessages-and-savemessages-signatures 的直接連結

getMessages()saveMessages() 方法的 signature 和傳回類型已變更。format overload 已移除,而這些方法現在一律使用 MastraDBMessage。此變更移除格式差異,令 API 更簡單。

要進行遷移,請使用 memory store、移除 format 參數,並更新程式碼以配合一致的傳回類型。

+ const memoryStore = await storage.getStore('memory');
+
// Always returns { messages: MastraDBMessage[] }
- const v1Messages = await storage.getMessages({ threadId, format: 'v1' });
- const v2Messages = await storage.getMessages({ threadId, format: 'v2' });
+ const result = await memoryStore?.getMessages({ threadId });
+ const messages = result?.messages; // MastraDBMessage[]

// SaveMessages always uses MastraDBMessage
- await storage.saveMessages({ messages: v1Messages, format: 'v1' });
- await storage.saveMessages({ messages: v2Messages, format: 'v2' });
+ const saveResult = await memoryStore?.saveMessages({ messages: mastraDBMessages });
+ const saved = saveResult?.messages; // MastraDBMessage[]

Vector store API 由位置參數改為具名參數
Vector store API 由位置參數改為具名參數 的直接連結

所有 vector store 方法現在都使用 arguments object,而非位置參數。這讓每個值的用途在呼叫位置一目了然,亦令 method signature 可在不依賴參數順序的情況下演進。

要進行遷移,請更新所有 vector store 方法呼叫,改用 arguments object。

- await vectorDB.createIndex(indexName, 3, 'cosine');
+ await vectorDB.createIndex({
+ indexName: indexName,
+ dimension: 3,
+ metric: 'cosine',
+ });

- await vectorDB.upsert(indexName, [[1, 2, 3]], [{ test: 'data' }]);
+ await vectorDB.upsert({
+ indexName: indexName,
+ vectors: [[1, 2, 3]],
+ metadata: [{ test: 'data' }],
+ });

- await vectorDB.query(indexName, [1, 2, 3], 5);
+ await vectorDB.query({
+ indexName: indexName,
+ queryVector: [1, 2, 3],
+ topK: 5,
+ });

重新命名 Vector store 方法
重新命名 Vector store 方法 的直接連結

updateIndexByIddeleteIndexById 方法已分別重新命名為 updateVectordeleteVector。新名稱清楚表示這些方法是對 vector 執行操作。

要進行遷移,請重新命名方法並傳入 arguments object。

- await vectorDB.updateIndexById(indexName, id, update);
- await vectorDB.deleteIndexById(indexName, id);
+ await vectorDB.updateVector({ indexName, id, update });
+ await vectorDB.deleteVector({ indexName, id });

PGVector constructor 由連接字串改為 object
PGVector constructor 由連接字串改為 object 的直接連結

PGVector constructor 現在需要 object 參數,而非連接字串。此變更令所有儲存 adapter 的 API 更一致。

要進行遷移,請將連接字串作為 object property 傳入。

- const pgVector = new PgVector(process.env.POSTGRES_CONNECTION_STRING!);
+ const pgVector = new PgVector({
+ connectionString: process.env.POSTGRES_CONNECTION_STRING,
+ });
Codemod

你可以使用 Mastra 的 codemod CLI 自動更新程式碼:

npx @mastra/codemod@latest v1/vector-pg-constructor .

PGVector defineIndex 改為 buildIndex
pgvector-defineindex-to-buildindex 的直接連結

defineIndex() 方法已移除,並以 buildIndex() 取代,清楚表示該方法會建立 index。

要進行遷移,請重新命名方法並傳入 arguments object。

- await vectorDB.defineIndex(indexName, 'cosine', { type: 'flat' });
+ await vectorDB.buildIndex({
+ indexName: indexName,
+ metric: 'cosine',
+ indexConfig: { type: 'flat' },
+ });

PostgresStoreschema 改為 schemaName
postgresstore-schema-to-schemaname 的直接連結

PostgresStore constructor 中的 schema 參數已重新命名為 schemaName。新名稱清楚表示該值是資料庫 schema 的名稱。

要進行遷移,請重新命名參數。

const pgStore = new PostgresStore({
connectionString: process.env.POSTGRES_CONNECTION_STRING,
- schema: customSchema,
+ schemaName: customSchema,
});
Codemod

你可以使用 Mastra 的 codemod CLI 自動更新程式碼:

npx @mastra/codemod@latest v1/storage-postgres-schema-name .

評分儲存方法改用 listScoresBy* 模式
score-storage-methods-to-listscoresby-pattern 的直接連結

評分儲存 API 已重新命名,以遵循 listScoresBy* 模式。此變更令它與更廣泛的 API 命名慣例保持一致。

要進行遷移,請將方法名稱由 getScores 更新為 listScoresByScorerId 及相關變體。

- const scores = await storage.getScores({ scorerName: 'helpfulness-scorer' });
+ const scores = await storage.listScoresByScorerId({
+ scorerId: 'helpfulness-scorer',
+ });
+ // Also available: listScoresByRunId, listScoresByEntityId, listScoresBySpan

已移除
已移除 的直接連結

非分頁儲存函數
非分頁儲存函數 的直接連結

非分頁儲存函數已移除,並由分頁版本取代。所有 list 操作現在都使用分頁,但你可以透過 perPage: false 擷取所有記錄。此變更令 API 保持一致,並防止意外載入大型資料集。

要進行遷移,請透過 domain store 使用分頁方法。要擷取所有記錄,請使用 perPage: false

- // Non-paginated direct access
- const messages = await storage.getMessages({ threadId });

+ // Use paginated methods via domain stores
+ const memoryStore = await storage.getStore('memory');
+ const result = await memoryStore?.listMessages({ threadId, page: 0, perPage: 20 });
+ // Or fetch all
+ const allMessages = await memoryStore?.listMessages({
+ threadId,
+ page: 0,
+ perPage: false,
+ });

getTracesgetTracesPaginated
gettraces-and-gettracespaginated 的直接連結

getTraces()getTracesPaginated() 方法已從儲存中移除。請使用 observability 套件存取 Trace,而非 core storage。

要進行遷移,請改用 observability 儲存方法。

- const traces = await storage.getTraces({ traceId: 'trace-123' });
- const paginated = await storage.getTracesPaginated({ page: 0, perPage: 20 });

+ // Use observability API for traces
+ import { initObservability } from '@mastra/observability';
+ const observability = initObservability({ config: { ... } });
+ // Access traces through observability API

Evals 測試工具
Evals 測試工具 的直接連結

Evals domain 測試工具已從 @internal/test-utils 移除。此變更反映舊有 Evals 功能已遭移除。

要進行遷移,請在測試時直接使用儲存 API,而非專門的 Evals 測試工具。

- import { createEvalsTests } from '@internal/test-utils/domains/evals';
- createEvalsTests({ storage });

+ // Use storage APIs directly for testing

從 MSSQL 儲存移除 TABLE_EVALS
從 MSSQL 儲存移除 TABLE_EVALS 的直接連結

MSSQL 儲存實作中的 TABLE_EVALS 資料表已移除。此變更反映舊有 Evals 功能已遭移除。

如果你正在將 MSSQL 儲存配合 Evals 使用,請遷移至另一個儲存 adapter,或移除 Evals 功能。