跳至主要內容

儲存空間保留

預設情況下,儲存空間會無限制增長。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。

參考實作包括 libSQLPostgreSQLMongoDB。其他 adapter 會永久保留資料列,直到實作 retention 為止。

使用範例
「使用範例」的直接連結

在任何 MastraCompositeStore(或擴充它的 adapter,例如 LibSQLStore)上宣告 retention,再從自己的 scheduler 呼叫 prune()

src/mastra/index.ts
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?:

RetentionConfig
各 domain、各資料表的資料存續時間政策。未設定的 domain 與資料表會永久保留。
RetentionConfig

[domain]?:

Record<TableKey, TableRetentionPolicy>
真實的儲存 domain key(例如 memoryobservability)。將該 domain 符合 retention 資格的資料表 key 對應至其政策。

TableRetentionPolicy
「TableRetentionPolicy」的直接連結

maxAge:

Duration
資料列的最長保留時間。anchor timestamp 嚴格早於 Date.now() - maxAge 的資料列可被刪除。數字代表毫秒,也可使用帶單位後綴的字串:mssmhdw(例如 '30d''12h')。

batchSize?:

number
= 1000
每批刪除的資料列數。每個批次都是獨立交易,可限制大型資料表的鎖定時間與 WAL 增長。

符合 retention 資格的資料表
「符合 retention 資格的資料表」的直接連結

每個 domain 都會宣告哪些資料表可依存續時間 prune,以及以哪個 timestamp 欄位作為比較 anchor。Anchor 的選擇會讓 maxAge 符合該資料的預期含義。Append-only log 使用建立時間,即時狀態則使用上次活動時間。工作與執行使用完成時間,因此進行中的工作絕不會被 prune。

Domain資料表 keyAnchor 欄位maxAge 衡量項目
memorythreadscreatedAtThread 存續時間
memorymessagescreatedAt訊息存續時間
memoryresourcescreatedAt資源存續時間
threadStatethreadStateupdatedAt閒置時間:仍在活動的 thread 狀態會保留
observabilityspansstartedAtSpan 存續時間
observabilitymetricstimestampMetric 事件存續時間(僅限 v-next)
observabilitylogstimestampLog 事件存續時間(僅限 v-next)
observabilityscorestimestamp分數事件存續時間(僅限 v-next)
observabilityfeedbacktimestamp意見回饋事件存續時間(僅限 v-next)
scoresscorerscreatedAt分數記錄存續時間
workflowsworkflowSnapshotupdatedAt閒置時間;已暫停或長時間執行的 Workflow 會保留
backgroundTasksbackgroundTaskscompletedAt完成後經過的時間;進行中的工作(NULL)絕不會被 prune
experimentsexperimentscompletedAt完成後經過的時間;執行中的實驗絕不會被 prune
notificationsnotificationscreatedAt通知存續時間
harnesssessionscreatedAtSession 記錄存續時間
schedulestriggersactual_fire_at觸發歷程存續時間(epoch-ms 欄位)
備註
  • Memory 的 observational_memory 資料表沒有 timestamp anchor,因此無法依存續時間 prune,也不是有效的 retention key。
  • 實驗會以完整單位 prune:達到存續時間的實驗會連同其結果資料列一起刪除(結果會隨 parent cascade),因此執行絕不會只刪除一部分。Retention 沒有獨立的 results key。
  • schedules 而言,增長資料表是觸發歷程(schedule_triggers,每次觸發一個資料列):排程定義屬於設定,不會被 prune。
  • 在 PostgreSQL 上,timestamp anchor 會使用具備時區資訊的 mirror 欄位(例如 createdAtZcompletedAtZ)。
  • LibSQL 與 PostgreSQL 支援上述所有 domain,但 PostgreSQL 未實作 harness。MongoDB 則支援 threadStateharness 以外的所有項目。
  • v-next PostgreSQL observability domain 會將 signal 事件儲存在按日分割的資料表(spansmetricslogsscoresfeedback)中。對此 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?:

number
每次呼叫中,各資料表的最大刪除批次數。達到上限時,該資料表的結果會以 done: false 傳回。

maxRows?:

number
每次呼叫中,各資料表刪除的最大資料列數。達到上限時,該資料表的結果會以 done: false 傳回。

pauseMs?:

number
批次之間的延遲(毫秒),以避免即時流量缺乏資源。

signal?:

AbortSignal
協作式取消。批次迴圈會在批次之間進行檢查並正常停止,傳回 done: false 的部分結果。

retention?:

RetentionConfig
僅針對此次呼叫取代 store 已設定的 retention 政策,例如略過某個 domain 或採用更積極的 prune。已設定的 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 執行的資料庫層級功能。

選擇 TTL 或 prune() 的時機

適合使用 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" })

libSQL 與 Turso

Turso Cloud 會代為管理儲存空間 compaction,因此不需要手動回收。這僅適用於自行託管的 libSQL 檔案。