第 34 篇
工程技术
历史索引清理:从直接删除到预览与复核
结合实际开发项目,讨论历史索引清理:从直接删除到预览与复核,说明设计理由、关键源码、验证方法及适用边界。
本文目录 · 8 节
本文属于 Memory Center:AI 编程上下文与记忆系统工程实践,第 34 篇,位于“存储与运维治理”模块。案例背景统一采用实际开发项目;复现步骤使用脱敏教学样例,不代表特定生产事故。
从实际问题出发
用户预览了可以删除的历史目录,随后另一个任务发布了新索引。若仍按旧预览直接删除,原先的候选可能已经变成需要保护的数据。清理是典型的“检查后状态又改变”问题。
预览不是永久授权快照
预览应绑定策略修订、候选指纹、当前代际和在途任务状态,并设置有效期。执行时重新核对,变化后要求重新预览。确认的是那一份具体计划,而不是所有未来目录。

图中展示的是本篇关注的职责和顺序。正常流程之外,还要结合下面的源码与实验检查失败分支;它不是对某次线上调用的运行时追踪。
清理与发布要协调
同一项目的清理、任务入队和代际发布需要共享适当的互斥边界。缓存还可能被运行中的进程使用。部分删除失败不能假装全部成功,也不能无限重放旧预览。
预览与执行之间的竞争
时刻一,预览把代际甲列为历史候选;时刻二,另一个操作改变当前代际或保留策略;时刻三,用户确认旧预览。正确做法是在执行点重新复核,不把人的确认当成状态没有变化的证明。
预览有效期、文件指纹与互斥边界分别解决不同问题:有效期限制陈旧时间,指纹发现内容变化,锁防止检查与删除之间再次竞争。三者都不能独立替代其他保护。
关键源码与阅读方法
源码基线为 Memory Center 6e90553(文档标注 5.0.0b24)。定位:memory_center/platform/storage_maintenance.py,函数 apply,起始行 131。
以下为源码节选,省略后续分支;不能作为独立脚本执行。
def apply(self, workspace_id, preview_id, revision, confirm=False):
self._root(workspace_id)
if not confirm:
raise CatalogError('CONFIRM_REQUIRED', '请先检查清理预览并明确确认')
with self.jobs.lifecycle_lock, FileLock(str(self.jobs.data_dir / 'jobs.lock'), timeout=5), self.store.generation_lock(workspace_id):
preview = self.store.get('storage_cleanup_preview', preview_id)
if preview['workspace_id'] != workspace_id:
raise CatalogError('FORBIDDEN', '清理预览不属于当前项目')
if preview['status'] != 'pending' or preview['revision'] != revision:
raise CatalogError('REVISION_CONFLICT', '清理预览已处理,请重新预览')
if datetime.fromisoformat(preview['expires_at']) <= datetime.now(timezone.utc):
raise CatalogError('STORAGE_PREVIEW_EXPIRED', '清理预览已过期,请重新预览')
try:
current = self._plan(workspace_id, preview['clear_parse_cache'], preview['clear_embedding_cache'])
except OSError as error:
raise CatalogError('STORAGE_READ_FAILED', '存储读取失败,未执行清理') from error
if any(preview[key] != value for key, value in current.items()):
raise CatalogError('STORAGE_PREVIEW_STALE', '任务、保留策略或文件已改变,请重新预览')
root = self._root(workspace_id)
removed, freed = [], 0
# 先持久化执行状态,部分失败也不可重放旧预览。下一次必须重新核对剩余目录。
self.store.update('storage_cleanup_preview', preview_id, {'status': 'applying'}, revision)
for candidate in current['candidates']:
try:
shutil.rmtree(root / candidate['path'])
removed.append(candidate['path'])
freed += candidate['bytes']
except OSError:
result = {'status': 'partial', 'removed': removed, 'freed_bytes': freed,
'message': '部分目录删除失败,请重新预览剩余目录后重试'}
self.store.update('storage_cleanup_preview', preview_id, result)
self.store.audit('admin', 'storage.cleanup.partial', workspace_id, preview_id)
return result
result = {'status': 'succeeded', 'removed': removed, 'freed_bytes': freed}
self.store.update('storage_cleanup_preview', preview_id, result)
self.store.audit('admin', 'storage.cleanup', workspace_id, preview_id)
阅读时先找输入由谁提供,再区分纯计算、读取和写入;遇到异常或提前返回,追问调用方会得到什么,以及已有状态是否仍然有效。上面的代码只证明展示范围内的实现,跨模块行为应继续查看调用处和相应测试。
用一个小实验核对设计
建议在独立临时目录和隔离数据中操作,使用虚构的接口、文档与项目名称。
- 在隔离数据目录生成预览。
- 修改保留策略或启动任务。
- 再提交原预览。
- 检查拒绝与实际删除范围。
**要核对的结果:**当前代际和在途资源应始终受保护,状态变化应使旧计划失效。
可把观察记录为下表;没有执行的项写“未执行”,不能用设计预期冒充实测结果。
| 核对项 | 应保留的证据 |
|---|---|
| 使用条件 | 源码版本、配置、输入与项目范围 |
| 正常行为 | 实际返回、来源位置或状态变化 |
| 关键失败分支 | 错误码、可见结果与原状态是否保留 |
| 结论范围 | 已覆盖、未覆盖和需要进一步验证的条件 |
适用边界与继续阅读
本篇以静态源码与项目设计为依据,给出可执行的核对方法,未据此宣称已经完成新的性能或生产环境测试。不同版本的入口、默认值和底层实现可能变化,应先核对当前合同,再把方法用于自己的实际开发项目。
理解这一节后,可以回到专题总导航,沿同一模块继续阅读;顺序学习时使用专栏内的上一篇和下一篇。涉及具体技术选择,应连同本篇的代价与限制一起采用。