보관 유지
기본적으로 스토리지는 무제한으로 늘어납니다. 보존은 선택 가능한 연령 기반 정리 시스템입니다. 테이블별로 선언합니다.maxAge정책retention구성한 다음 호출하세요.storage.prune()구성된 기간보다 오래된 행을 삭제합니다. 구성하지 않은 모든 항목은 영원히 유지되므로 선택하기 전까지는 동작이 변경되지 않습니다.
prune()은 행을 삭제합니다. 이를 통해 증가를 제한할 수 있으며 대규모 테이블에서도 안전하게 실행할 수 있습니다(일괄 처리, 제한, 재개 및 취소 가능). 디스크 공간 자체는 회수하지 않습니다. SQLite/libSQL에서는 해제된 페이지가 이후 쓰기에 재사용되므로 파일 크기가 더 커지지는 않지만, 디스크를 OS에 반환하는 작업(예: VACUUM)은 기본 데이터베이스와 운영자가 관리해야 합니다.
보존은 정상적인 작업의 부수 효과로 행이 무제한 누적되는 증가 테이블만 다룹니다(대화 기록, 텔레메트리, 작업 및 실행 레코드, 일정 실행 기록, 이벤트 피드). 사용자가 작성한 아티팩트와 구성(Agent, Skill, Workspace, Prompt 블록, 데이터세트, 일정 정의, 채널 설치 등)은 사용자의 의도에 따라 증가하며 명시적으로 편집하거나 삭제하므로 유효한 보존 키가 아닙니다.
참조 구현은 libSQL, PostgreSQL, MongoDB입니다. 다른 어댑터는 보존 기능을 구현할 때까지 행을 영구적으로 유지합니다.
사용예사용예에 대한 직접 링크
MastraCompositeStore 또는 이를 확장하는 어댑터(예: LibSQLStore)에 retention을 선언한 다음, 자체 스케줄러에서 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은 완전히 타입이 지정되어 있습니다. 키는 실제 도메인 키여야 하며 각 테이블 키는 해당 도메인이 보존 대상으로 선언한 키여야 합니다. 객체를 저장소 구성에 직접 전달하면 타입 검사가 수행됩니다. 독립형으로 구성하는 경우 알 수 없는 도메인이나 테이블이 컴파일 오류가 되도록 satisfies RetentionConfig를 사용하세요.
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?:
[domain]?:
memory, observability). 해당 도메인에서 보존 대상인 테이블 키를 정책에 매핑합니다.테이블보존정책테이블보존정책에 대한 직접 링크
maxAge:
Date.now() - maxAge보다 엄격하게 오래된 행은 삭제 대상이 됩니다. 숫자는 밀리초를 나타내며, 문자열에는 ms, s, m, h, d, w 단위 접미사를 사용할 수 있습니다(예: '30d', '12h').batchSize?:
보존 적격 테이블보존 적격 테이블에 대한 직접 링크
각 도메인은 테이블 중 기간 기반 정리가 가능한 테이블과 비교 기준이 되는 타임스탬프 열을 선언합니다. 앵커는 해당 데이터에 대해 maxAge가 예상한 의미를 갖도록 선택됩니다. 추가 전용 로그는 생성 시각을 사용하고, 활성 상태는 마지막 활동 시각을 사용합니다. 작업과 실행은 완료 시각을 사용하므로 진행 중인 작업은 정리되지 않습니다.
| 도메인 | 테이블 키 | 앵커 컬럼 | maxAge measures |
|---|---|---|---|
memory | threads | createdAt | Thread age |
memory | messages | createdAt | Message age |
memory | resources | createdAt | 자원연령 |
threadState | threadState | updatedAt | 비활성: 여전히 활성 상태인 스레드의 상태가 유지됩니다. |
observability | spans | startedAt | 스팬 연령 |
observability | metrics | timestamp | 측정항목 이벤트 기간(v-next에만 해당) |
observability | logs | timestamp | 로그 이벤트 기간(v-next에만 해당) |
observability | scores | timestamp | 점수 이벤트 연령(v-next에만 해당) |
observability | feedback | timestamp | 피드백 이벤트 기간(v-next에만 해당) |
scores | scorers | createdAt | 점수 기록 연령 |
workflows | workflowSnapshot | updatedAt | 비활성, 일시 중단 또는 장기 실행 Workflow가 유지됨 |
backgroundTasks | backgroundTasks | completedAt | 완료 이후 시간, 진행 중인 작업(NULL)은 절대 가지치기되지 않습니다 |
experiments | experiments | completedAt | 완료 이후 시간, 실행 중인 실험은 절대 정리되지 않습니다 |
notifications | notifications | createdAt | 알림 연령 |
harness | sessions | createdAt | 세션 기록 연령 |
schedules | triggers | actual_fire_at | 화재 역사 시대(epoch-ms 열) |
- Memory의
observational_memory테이블에는 타임스탬프 앵커가 없으므로 기간 기반 정리를 수행할 수 없으며 유효한 보존 키가 아닙니다. - 실험은 전체 단위로 정리됩니다. 오래된 실험의 결과 행도 함께 삭제되므로(결과가 상위 항목과 함께 연쇄 삭제됨) 실행이 부분적으로 삭제된 상태로 남지 않습니다. 별도의
results보존 키는 없습니다. schedules의 증가 테이블은 실행 기록(schedule_triggers, 실행당 한 행)입니다. 일정 정의는 구성이므로 정리되지 않습니다.- PostgreSQL에서 타임스탬프 앵커는 시간대를 인식하는 미러 열을 사용합니다(예:
createdAtZ,completedAtZ). - LibSQL과 PostgreSQL은 위의 모든 도메인을 지원하지만, PostgreSQL이 구현하지 않는
harness는 제외됩니다. MongoDB는threadState와harness를 제외한 모든 도메인을 지원합니다. - v-next PostgreSQL Observability 도메인은 신호 이벤트를 일별 파티션 테이블에 저장합니다(
spans,metrics,logs,scores,feedback). 여기서prune()은 행을 삭제하는 대신 기준 시각보다 전체가 오래된 일별 파티션(또는 TimescaleDB 청크)을 통째로 삭제합니다. 실질적인 세분성은 하루이며, 파티션의 하루 전체가maxAge를 지난 후에만 삭제됩니다.PruneResult.deleted는 삭제된 파티션에 있던 행 수를 보고합니다.
행동 양식행동 양식에 대한 직접 링크
보유보유에 대한 직접 링크
prune(options?)pruneoptions에 대한 직접 링크
retention에 정책이 있는 모든 도메인에서 구성된 maxAge보다 오래된 행을 삭제합니다. 처리된 테이블마다 하나의 PruneResult를 반환합니다. 구성된 retention이 없으면 아무 작업도 하지 않고 []를 반환합니다.
prune()수백만 개의 행이 있는 테이블에서 안전하도록 설계되었습니다. 제한된 일괄 처리 청크(각 일괄 처리는 자체 트랜잭션임)로 삭제하므로 긴 잠금이 필요하지 않거나 트랜잭션 로그가 부풀어오르지 않습니다. 결코 실행되지 않습니다VACUUM.
해당 호출에 한해서만 구성된 정책을 대체하려면 options.retention을 전달하세요. 예를 들어 특정 도메인을 건너뛰거나(채팅 기록 유지) 기존 구성보다 더 적극적으로 정리할 수 있습니다. 저장소에 구성된 retention은 변경되지 않습니다.
앵커 열 인덱스는 정책이 있는 각 테이블에서 첫 번째 prune() 호출 시 지연 생성되며(init()에서는 생성되지 않음), 보존 기능을 구성하지 않은 배포에는 추가적인 인덱스 쓰기나 디스크 오버헤드가 발생하지 않습니다. 기존 대규모 테이블을 처음 정리할 때 일회성 인덱스 빌드 비용이 발생합니다. 이후 정리에서는 해당 인덱스를 재사용합니다.
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' } },
},
})
보고:Promise<PruneResult[]>
정리 옵션정리 옵션에 대한 직접 링크
maxBatches?:
done: false와 함께 반환됩니다.maxRows?:
done: false와 함께 반환됩니다.pauseMs?:
signal?:
done: false와 함께 부분 결과를 반환합니다.retention?:
retention은 변경되지 않습니다.정리결과정리결과에 대한 직접 링크
각 결과는 한 테이블의 진행 상황을 설명합니다.
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()에는 기본 제공 스케줄러가 없습니다. 실행 시기를 직접 결정하세요. 제한이 적용될 수 있으므로 한 번의 호출로 모든 항목을 삭제하지 못할 수 있습니다. 결과가 done: false이면 대상 행이 남아 있으므로 다음 틱에서 다시 호출합니다. 이렇게 하면 각 호출을 짧게 유지하면서 대규모 백로그를 여러 번의 실행에 걸쳐 소진할 수 있습니다.
// 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을 사용하여 장시간 실행되는 정리를 취소할 수도 있습니다. 루프는 일괄 처리 사이에서 중지되고 done: false와 함께 부분 결과를 반환하므로 다음 실행에서 정상적으로 재개됩니다.
MongoDB TTL 인덱스(프룬 대체)MongoDB TTL 인덱스(프룬 대체)에 대한 직접 링크
MongoDB는 prune()을 수동으로 호출하지 않아도 만료된 문서를 자동으로 삭제하는 기본 TTL(Time-To-Live) 인덱스를 제공합니다. 이는 백그라운드 스레드로 실행되는 데이터베이스 수준의 기능입니다.
:::note[TTL과 prune()을 사용하는 경우]
다음과 같은 경우 MongoDB TTL 인덱스를 사용하세요.
- 유지 관리가 필요 없는 자동화된 삭제를 원합니다.
- 보관 기간은 고정되어 있습니다(예: "항상 30일").
- 데이터베이스 기반 솔루션을 선호합니다.
다음과 같은 경우 prune()을 사용하세요:
- 삭제 시기를 세밀하게 제어해야 합니다.
- 업무 시간 동안 삭제 비율을 제한하고 싶습니다.
- 재개 가능하고 취소 가능한 정리 작업이 필요합니다.
- 여러 데이터베이스가 포함된 복합 스토리지를 사용하고 있습니다.
두 가지 접근 방식 모두 유효합니다. TTL이 더 간단합니다.prune() gives more control.
:::
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 인덱스는 문서가 만료된 직후 문서를 삭제하지만(백그라운드 스레드는 ~60초마다 실행됨) 정확한 타이밍은 보장되지 않습니다. 정확하고 즉각적인 정리를 위해 다음을 사용하세요.prune() instead.
디스크 회수 중디스크 회수 중에 대한 직접 링크
prune()행을 삭제하지만 데이터베이스 파일을 축소하지는 않습니다. SQLite/libSQL에서 해제된 페이지는 사용 가능 목록에 추가되고 향후 쓰기에서 재사용되므로 파일 크기가 더 이상 커지지 않습니다. 대부분의 사용자에게는 이 방법만으로도 무한한 크기 증가 문제가 해결됩니다.
여유 공간을 OS에 반환하는 것은 Mastra가 관리하지 않는 별개의 문제입니다. 파일을 명시적으로 축소해야 한다면 유지 관리 시간에 기본 데이터베이스의 압축 작업(예: 자체 호스팅 libSQL의 VACUUM)을 직접 실행하세요. 전체 VACUUM은 파일을 잠그며 파일 크기의 약 두 배에 해당하는 여유 디스크 공간이 필요합니다. PostgreSQL에서는 autovacuum이 사용 중지된 튜플을 자동으로 회수해 재사용합니다. 디스크를 OS에 반환해야 할 때만 수동 VACUUM FULL이 필요합니다.
MongoDB에서는 삭제된 문서의 공간이 이후 삽입에 재사용됩니다. 디스크 공간을 회수하려면 유지 관리 시간에 db.runCommand({ compact: "collection_name" })를 실행하세요.
:::참고[libSQL 및 Turso]Turso Cloud는 저장소 압축을 자동으로 관리하므로 수동으로 회수할 공간이 없습니다. 이는 자체 호스팅 libSQL 파일에만 적용됩니다.
:::