儲存空間保留
預設情況下,儲存空間會無限制增長。Retention 是選用、以資料存續時間為基礎的清理系統:在 retention 設定中為各資料表宣告 maxAge 政策,再呼叫 storage.prune(),刪除早於設定存續時間的資料列。未設定的所有內容都會永久保留,因此在選擇啟用前不會有任何行為變更。
prune() 會刪除資料列。它可限制增長,並能安全地在大型資料表上執行(批次、有界、可繼續、可取消)。它絕不回收磁碟空間:在 SQLite/libSQL 上,釋放的 page 會供日後寫入重複使用,因此檔案會停止增長;但將磁碟空間歸還給 OS(例如執行 VACUUM)則由底層資料庫與 operator 管理。
Retention 僅涵蓋增長資料表:因正常操作的副作用而無限制累積資料列的資料表(對話歷程、遙測、工作與執行記錄、排程觸發歷程、事件 feed)。使用者建立的 artifact 與設定(Agent、Skill、Workspace、prompt block、dataset、排程定義、channel installation 等)會依使用者意圖增長,並由使用者明確編輯或刪除,因此不是有效的 retention key。
參考實作包括 libSQL、PostgreSQL 與 MongoDB。其他 adapter 會永久保留資料列,直到實作 retention 為止。
使用範例「使用範例」的直接連結
在任何 MastraCompositeStore(或擴充它的 adapter,例如 LibSQLStore)上宣告 retention,再從自己的 scheduler 呼叫 prune()。
import { LibSQLStore } from '@mastra/libsql'
const storage = new LibSQLStore({
id: 'mastra-storage',
url: 'file:./mastra.db',
retention: {
memory: {
messages: { maxAge: '30d' },
threads: { maxAge: '90d', batchSize: 500 },
},
observability: {
spans: { maxAge: '7d' },
},
},
})
// Wire this to your own cron/scheduler: Mastra never runs it for you.
const results = await storage.prune()
retention 具有完整型別。Key 必須是真實的 domain key,而每個資料表 key 都必須是 domain 宣告符合 retention 資格的項目。將物件直接傳入 store 設定會進行型別檢查;若獨立建立,請使用 satisfies RetentionConfig,讓未知 domain 或資料表成為編譯錯誤:
import type { RetentionConfig } from '@mastra/core/storage'
const retention = {
memory: {
messages: { maxAge: '30d' }, // ok
bogus: { maxAge: '30d' }, // Error: not a memory retention table
},
bogusDomain: {}, // Error: not a storage domain
} satisfies RetentionConfig
Retention 設定「Retention 設定」的直接連結
在 store 設定上設定 retention 欄位。
retention?:
[domain]?:
memory、observability)。將該 domain 符合 retention 資格的資料表 key 對應至其政策。TableRetentionPolicy「TableRetentionPolicy」的直接連結
maxAge:
Date.now() - maxAge 的資料列可被刪除。數字代表毫秒,也可使用帶單位後綴的字串:ms、s、m、h、d、w(例如 '30d'、'12h')。batchSize?:
符合 retention 資格的資料表「符合 retention 資格的資料表」的直接連結
每個 domain 都會宣告哪些資料表可依存續時間 prune,以及以哪個 timestamp 欄位作為比較 anchor。Anchor 的選擇會讓 maxAge 符合該資料的預期含義。Append-only log 使用建立時間,即時狀態則使用上次活動時間。工作與執行使用完成時間,因此進行中的工作絕不會被 prune。
| Domain | 資料表 key | Anchor 欄位 | maxAge 衡量項目 |
|---|---|---|---|
memory | threads | createdAt | Thread 存續時間 |
memory | messages | createdAt | 訊息存續時間 |
memory | resources | createdAt | 資源存續時間 |
threadState | threadState | updatedAt | 閒置時間:仍在活動的 thread 狀態會保留 |
observability | spans | startedAt | Span 存續時間 |
observability | metrics | timestamp | Metric 事件存續時間(僅限 v-next) |
observability | logs | timestamp | Log 事件存續時間(僅限 v-next) |
observability | scores | timestamp | 分數事件存續時間(僅限 v-next) |
observability | feedback | timestamp | 意見回饋事件存續時間(僅限 v-next) |
scores | scorers | createdAt | 分數記錄存續時間 |
workflows | workflowSnapshot | updatedAt | 閒置時間;已暫停或長時間執行的 Workflow 會保留 |
backgroundTasks | backgroundTasks | completedAt | 完成後經過的時間;進行中的工作(NULL)絕不會被 prune |
experiments | experiments | completedAt | 完成後經過的時間;執行中的實驗絕不會被 prune |
notifications | notifications | createdAt | 通知存續時間 |
harness | sessions | createdAt | Session 記錄存續時間 |
schedules | triggers | actual_fire_at | 觸發歷程存續時間(epoch-ms 欄位) |
- Memory 的
observational_memory資料表沒有 timestamp anchor,因此無法依存續時間 prune,也不是有效的 retention key。 - 實驗會以完整單位 prune:達到存續時間的實驗會連同其結果資料列一起刪除(結果會隨 parent cascade),因此執行絕不會只刪除一部分。Retention 沒有獨立的
resultskey。 - 對
schedules而言,增長資料表是觸發歷程(schedule_triggers,每次觸發一個資料列):排程定義屬於設定,不會被 prune。 - 在 PostgreSQL 上,timestamp anchor 會使用具備時區資訊的 mirror 欄位(例如
createdAtZ、completedAtZ)。 - LibSQL 與 PostgreSQL 支援上述所有 domain,但 PostgreSQL 未實作
harness。MongoDB 則支援threadState與harness以外的所有項目。 - v-next PostgreSQL observability domain 會將 signal 事件儲存在按日分割的資料表(
spans、metrics、logs、scores、feedback)中。對此 domain 而言,prune()不會逐列刪除,而是刪除整個完全早於截止時間的每日 partition(或 TimescaleDB chunk)。其有效精細度為一天,只有整天都超過maxAge後才會刪除該 partition。PruneResult.deleted會回報被刪除 partition 中的資料列數。
Methods「Methods」的直接連結
Retention「Retention」的直接連結
prune(options?)「pruneoptions」的直接連結
在 retention 中設有政策的每個 domain 內,刪除早於設定 maxAge 的資料列。每個處理過的資料表會傳回一個 PruneResult。未設定 retention 時不會執行任何操作,並傳回 []。
prune() 的設計可安全用於具有數百萬筆資料列的資料表。它會以有界批次區塊刪除(每批都是獨立交易),因此絕不會長時間鎖定或使 transaction log 膨脹。它絕不執行 VACUUM。
傳入 options.retention 可只針對該次呼叫取代已設定的政策,例如略過某個 domain(保留聊天歷程),或採用比常設設定更積極的 prune。Store 已設定的 retention 不會變更。
對於每個設有政策的資料表,anchor 欄位索引會在首次呼叫 prune() 時延遲建立(絕不在 init() 時建立),因此未設定 retention 的部署不會產生額外索引寫入或磁碟負擔。首次 prune 現有大型資料表時會有一次性索引建立成本,後續 prune 則會重複使用該索引。
const results = await storage.prune({
maxRows: 50_000, // cap work this call
pauseMs: 50, // breathe between batches
})
for (const r of results) {
console.log(`${r.domain}.${r.table}: deleted ${r.deleted}, done=${r.done}`)
}
// One-off pass with different policies (configured retention untouched):
await storage.prune({
retention: {
observability: { spans: { maxAge: '1d' } },
},
})
Returns: Promise<PruneResult[]>
PruneOptions「PruneOptions」的直接連結
maxBatches?:
done: false 傳回。maxRows?:
done: false 傳回。pauseMs?:
signal?:
done: false 的部分結果。retention?:
retention 不會變更。PruneResult「PruneResult」的直接連結
每個結果說明一個資料表的進度:
interface PruneResult {
domain: string // e.g. 'memory'
table: string // physical table name, e.g. 'mastra_messages'
deleted: number // rows deleted during this call
done: boolean // false => eligible rows remain; call prune() again
}
依排程執行 prune「依排程執行 prune」的直接連結
prune() 沒有內建 scheduler:由你決定執行時機。由於它是有界的,單次呼叫可能無法刪除所有內容。任何結果為 done: false 時,表示仍有符合資格的資料列,請在下一個 tick 再次呼叫。如此可縮短每次呼叫時間,並在多次執行中逐步清除大量 backlog。
// Runs on your own cron (node-cron, a workflow schedule, an external job, etc.).
async function retentionTick() {
const results = await storage.prune({ maxRows: 100_000, pauseMs: 25 })
const incomplete = results.filter(r => !r.done)
if (incomplete.length) {
// Rows remain; the next scheduled tick will continue where this one stopped.
console.log(
'retention still draining:',
incomplete.map(r => `${r.domain}.${r.table}`),
)
}
}
你也可以使用 AbortSignal 取消長時間執行的 prune:迴圈會在批次之間停止,並傳回 done: false 的部分結果,讓下次執行可順利繼續。
MongoDB TTL 索引(prune 的替代方案)「MongoDB TTL 索引(prune 的替代方案)」的直接連結
MongoDB 提供原生 TTL(Time-To-Live)索引,可自動刪除到期文件,無需手動呼叫 prune()。這是以背景 thread 執行的資料庫層級功能。
適合使用 MongoDB TTL 索引的情況:
- 希望自動刪除,無需維護
- 保留期間固定(例如「一律 30 天」)
- 偏好資料庫原生解決方案
適合使用 prune() 的情況:
- 需要細緻控制刪除時間
- 希望在營業時間限制刪除速率
- 需要可繼續、可取消的清理操作
- 使用包含多個資料庫的複合儲存空間
兩種方式都有效。TTL 較簡單;prune() 則提供更多控制能力。
在 MongoDB 上設定 TTL 索引「在 MongoDB 上設定 TTL 索引」的直接連結
TTL 索引作用於日期欄位。MongoDB 每 60 秒檢查一次索引,並刪除「日期欄位 + TTL 期間」小於目前時間的文件。
import { MongoDBStore } from '@mastra/mongodb'
const storage = new MongoDBStore({
id: 'mongodb-storage',
uri: process.env.MONGODB_URI!,
dbName: process.env.MONGODB_DB_NAME!,
indexes: [
// Messages expire after 30 days
{
collection: 'mastra_messages',
keys: { createdAt: 1 },
options: { expireAfterSeconds: 30 * 24 * 60 * 60 }, // 30 days
},
// Threads expire after 90 days
{
collection: 'mastra_threads',
keys: { createdAt: 1 },
options: { expireAfterSeconds: 90 * 24 * 60 * 60 }, // 90 days
},
// Spans expire after 7 days
{
collection: 'mastra_ai_spans',
keys: { startedAt: 1 },
options: { expireAfterSeconds: 7 * 24 * 60 * 60 }, // 7 days
},
],
})
TTL 索引會在文件到期後不久刪除文件(背景 thread 約每 60 秒執行一次),但不保證確切時間。如需精確、立即的清理,請改用 prune()。
回收磁碟空間「回收磁碟空間」的直接連結
prune() 會刪除資料列,但不會縮小資料庫檔案。在 SQLite/libSQL 上,釋放的 page 會進入 freelist,供日後寫入重複使用,因此檔案會停止增長;對大多數使用者而言,光是這點就能解決無限制增長的問題。
將可用空間歸還給 OS 是另一項工作,Mastra 不會管理。若確實需要縮小檔案,請在維護期間自行執行底層資料庫的 compaction(例如在自行託管的 libSQL 上執行 VACUUM)。完整 VACUUM 會鎖定檔案,並需要約為檔案大小兩倍的可用磁碟空間。在 PostgreSQL 上,autovacuum 會自動回收 dead tuple 以供重複使用;只有必須將磁碟空間歸還給 OS 時,才需要手動執行 VACUUM FULL。
對 MongoDB 而言,已刪除文件的空間會供日後插入重複使用。若要回收磁碟空間,請在維護期間執行 db.runCommand({ compact: "collection_name" })。
Turso Cloud 會代為管理儲存空間 compaction,因此不需要手動回收。這僅適用於自行託管的 libSQL 檔案。