> Discover all available pages from the documentation index: https://mastra.zisheng.pro/ko/llms.txt # 보관 유지 기본적으로 스토리지는 무제한으로 늘어납니다. 보존은 선택 가능한 연령 기반 정리 시스템입니다. 테이블별로 선언합니다.`maxAge`정책`retention`구성한 다음 호출하세요.`storage.prune()`구성된 기간보다 오래된 행을 삭제합니다. 구성하지 않은 모든 항목은 영원히 유지되므로 선택하기 전까지는 동작이 변경되지 않습니다. `prune()`은 행을 삭제합니다. 이를 통해 증가를 제한할 수 있으며 대규모 테이블에서도 안전하게 실행할 수 있습니다(일괄 처리, 제한, 재개 및 취소 가능). 디스크 공간 자체는 회수하지 않습니다. SQLite/libSQL에서는 해제된 페이지가 이후 쓰기에 재사용되므로 파일 크기가 더 커지지는 않지만, 디스크를 OS에 반환하는 작업(예: `VACUUM`)은 기본 데이터베이스와 운영자가 관리해야 합니다. 보존은 정상적인 작업의 부수 효과로 행이 무제한 누적되는 **증가 테이블**만 다룹니다(대화 기록, 텔레메트리, 작업 및 실행 레코드, 일정 실행 기록, 이벤트 피드). 사용자가 작성한 아티팩트와 구성(Agent, Skill, Workspace, Prompt 블록, 데이터세트, 일정 정의, 채널 설치 등)은 사용자의 의도에 따라 증가하며 명시적으로 편집하거나 삭제하므로 유효한 보존 키가 아닙니다. 참조 구현은 [libSQL](https://mastra.zisheng.pro/ko/reference/storage/libsql), [PostgreSQL](https://mastra.zisheng.pro/ko/reference/storage/postgresql), [MongoDB](https://mastra.zisheng.pro/ko/reference/storage/mongodb)입니다. 다른 어댑터는 보존 기능을 구현할 때까지 행을 영구적으로 유지합니다. ## 사용예 `MastraCompositeStore` 또는 이를 확장하는 어댑터(예: `LibSQLStore`)에 `retention`을 선언한 다음, 자체 스케줄러에서 `prune()`을 호출하세요. ```typescript 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`를 사용하세요. ```typescript 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** (`RetentionConfig`): 도메인 및 테이블별 보존 기간 정책입니다. 설정되지 않은 도메인과 테이블은 영구적으로 유지됩니다. **retention.\[domain]** (`Record`): 실제 저장소 도메인 키입니다(예: memory, observability). 해당 도메인에서 보존 대상인 테이블 키를 정책에 매핑합니다. ### 테이블보존정책 **maxAge** (`Duration`): 행을 유지할 최대 기간입니다. 기준 타임스탬프가 Date.now() - maxAge보다 엄격하게 오래된 행은 삭제 대상이 됩니다. 숫자는 밀리초를 나타내며, 문자열에는 ms, s, m, h, d, w 단위 접미사를 사용할 수 있습니다(예: '30d', '12h'). **batchSize** (`number`): 일괄 처리당 삭제되는 행 수입니다. 각 일괄 처리는 독립된 트랜잭션이므로 대규모 테이블에서 잠금 지속 시간과 WAL 증가량을 제한합니다. (Default: `1000`) ### 보존 적격 테이블 각 도메인은 테이블 중 기간 기반 정리가 가능한 테이블과 비교 기준이 되는 타임스탬프 열을 선언합니다. 앵커는 해당 데이터에 대해 `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?)` `retention`에 정책이 있는 모든 도메인에서 구성된 `maxAge`보다 오래된 행을 삭제합니다. 처리된 테이블마다 하나의 `PruneResult`를 반환합니다. 구성된 `retention`이 없으면 아무 작업도 하지 않고 `[]`를 반환합니다. `prune()`수백만 개의 행이 있는 테이블에서 안전하도록 설계되었습니다. 제한된 일괄 처리 청크(각 일괄 처리는 자체 트랜잭션임)로 삭제하므로 긴 잠금이 필요하지 않거나 트랜잭션 로그가 부풀어오르지 않습니다. 결코 실행되지 않습니다`VACUUM`. 해당 호출에 한해서만 구성된 정책을 대체하려면 `options.retention`을 전달하세요. 예를 들어 특정 도메인을 건너뛰거나(채팅 기록 유지) 기존 구성보다 더 적극적으로 정리할 수 있습니다. 저장소에 구성된 `retention`은 변경되지 않습니다. 앵커 열 인덱스는 정책이 있는 각 테이블에서 첫 번째 `prune()` 호출 시 지연 생성되며(`init()`에서는 생성되지 않음), 보존 기능을 구성하지 않은 배포에는 추가적인 인덱스 쓰기나 디스크 오버헤드가 발생하지 않습니다. 기존 대규모 테이블을 처음 정리할 때 일회성 인덱스 빌드 비용이 발생합니다. 이후 정리에서는 해당 인덱스를 재사용합니다. ```typescript 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` ##### 정리 옵션 **maxBatches** (`number`): 호출당 테이블별 최대 삭제 일괄 처리 수입니다. 이 한도에 도달하면 해당 테이블의 결과가 done: false와 함께 반환됩니다. **maxRows** (`number`): 호출당 테이블별 최대 삭제 행 수입니다. 이 한도에 도달하면 해당 테이블의 결과가 done: false와 함께 반환됩니다. **pauseMs** (`number`): 실시간 트래픽이 리소스를 확보하지 못하는 상황을 방지하기 위한 일괄 처리 사이의 지연 시간(밀리초)입니다. **signal** (`AbortSignal`): 협력적 취소입니다. 일괄 처리 루프가 각 일괄 처리 사이에 이를 확인하고 정상적으로 중지하며, done: false와 함께 부분 결과를 반환합니다. **retention** (`RetentionConfig`): 해당 호출에 한해서만 저장소에 구성된 보존 정책을 대체합니다. 예를 들어 도메인을 건너뛰거나 더 적극적으로 정리할 수 있습니다. 구성된 retention은 변경되지 않습니다. ##### 정리결과 각 결과는 한 테이블의 진행 상황을 설명합니다. ```typescript 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`이면 대상 행이 남아 있으므로 다음 틱에서 다시 호출합니다. 이렇게 하면 각 호출을 짧게 유지하면서 대규모 백로그를 여러 번의 실행에 걸쳐 소진할 수 있습니다. ```typescript // 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는 `prune()`을 수동으로 호출하지 않아도 만료된 문서를 자동으로 삭제하는 기본 [TTL(Time-To-Live) 인덱스](https://www.mongodb.com/docs/manual/core/index-ttl/)를 제공합니다. 이는 백그라운드 스레드로 실행되는 데이터베이스 수준의 기능입니다. :::note\[TTL과 prune()을 사용하는 경우] **다음과 같은 경우 MongoDB TTL 인덱스를 사용하세요.** - 유지 관리가 필요 없는 자동화된 삭제를 원합니다. - 보관 기간은 고정되어 있습니다(예: "항상 30일"). - 데이터베이스 기반 솔루션을 선호합니다. **다음과 같은 경우 `prune()`을 사용하세요:** - 삭제 시기를 세밀하게 제어해야 합니다. - 업무 시간 동안 삭제 비율을 제한하고 싶습니다. - 재개 가능하고 취소 가능한 정리 작업이 필요합니다. - 여러 데이터베이스가 포함된 복합 스토리지를 사용하고 있습니다. 두 가지 접근 방식 모두 유효합니다. TTL이 더 간단합니다.`prune()` gives more control. ::: ### MongoDB에서 TTL 인덱스 설정 TTL 색인은 날짜 필드에서 작동합니다. MongoDB는 60초마다 인덱스를 확인하고 날짜 필드 + TTL 기간 < 현재 시간인 문서를 삭제합니다. ```typescript 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" })`](https://www.mongodb.com/docs/manual/reference/command/compact/)를 실행하세요. :::참고\[libSQL 및 Turso][Turso Cloud](https://mastra.zisheng.pro/ko/reference/storage/libsql)는 저장소 압축을 자동으로 관리하므로 수동으로 회수할 공간이 없습니다. 이는 자체 호스팅 libSQL 파일에만 적용됩니다. ::: ## 관련된 - [libSQL 스토리지](https://mastra.zisheng.pro/ko/reference/storage/libsql) - [PostgreSQL 스토리지](https://mastra.zisheng.pro/ko/reference/storage/postgresql) - [복합 스토리지](https://mastra.zisheng.pro/ko/reference/storage/composite) - [스토리지 개요](https://mastra.zisheng.pro/ko/reference/storage/overview)