第 15 篇
工程技术
增量索引如何复用旧事实,又避免复用损坏结果
结合实际开发项目,讨论增量索引如何复用旧事实,又避免复用损坏结果,说明设计理由、关键源码、验证方法及适用边界。
本文目录 · 8 节
本文属于 Memory Center:AI 编程上下文与记忆系统工程实践,第 15 篇,位于“索引与代码事实”模块。案例背景统一采用实际开发项目;复现步骤使用脱敏教学样例,不代表特定生产事故。
从实际问题出发
大项目只修改少数文件时,重新解析全部源码成本很高。但旧索引基线如果损坏或规则已变化,盲目复用会把错误事实带进新版本。
先验证复用条件
源清单用于识别变化,服务划分和结构规则也会影响复用是否有效。加载基线时校验目录、清单结构和事实库;复用不是看到缓存存在就直接相信。

图中展示的是本篇关注的职责和顺序。正常流程之外,还要结合下面的源码与实验检查失败分支;它不是对某次线上调用的运行时追踪。
回退必须清理半成品
复制部分旧数据后才发现失败,不能继续把这些文件当作完整基线。当前实现会删除新代际里部分复制的 index 与 graph,再按无基线的全量计划继续。代价是失败时增加一次工作,但避免发布混合状态。
四种变化不能混为一谈
文件内容变化通常对应重新解析;文件删除还要求清理旧符号和引用;服务归属变化可能改变整个解析范围;解析规则升级则可能使全部旧结构失效。增量系统最难的部分并非找到变化的字节,而是找出所有受这些变化影响的派生事实。
若旧基线无法被证明完整,全量回退是一条可解释的安全路径。它以额外计算换取结果一致性,不能为了漂亮的增量比例继续沿用可疑状态。
关键源码与阅读方法
源码基线为 Memory Center 6e90553(文档标注 5.0.0b24)。定位:memory_center/platform/incremental_index.py,函数 load_baseline,起始行 72。
以下为当前基线中的函数代码;依赖项目模块,不能作为独立脚本执行。
def load_baseline(store, workspace_id, directory, current, services):
with store.generation_lock(workspace_id):
try:
index = store.get('index', workspace_id)
previous_dir = Path(index['workspace_file']).parent
expected = store.db.data_dir / 'workspaces' / workspace_id / 'generations'
if previous_dir.parent != expected or previous_dir.is_symlink():
raise ValueError('基线路径不匹配')
manifest_file = previous_dir / 'source-manifest.json'
if manifest_file.is_symlink():
raise ValueError('基线清单含符号链接')
previous = json.loads(manifest_file.read_text())
if not isinstance(previous, dict) or not isinstance(previous.get('files'), dict):
raise ValueError('基线清单无效')
for path, row in previous['files'].items():
relative = Path(path)
if relative.is_absolute() or '..' in relative.parts or not isinstance(row, dict) or not isinstance(row.get('hash'), str) or row.get('service') not in ['', *services]:
raise ValueError('基线文件无效')
plan = plan_incremental(previous, current, services)
if plan['mode'] != 'full':
seed_facts(previous_dir, directory)
plan['baseline_generation'] = index['generation']
return plan, previous
except (CatalogError, OSError, ValueError, KeyError, TypeError, sqlite3.Error):
# 损坏或旧基线回退全量,不让部分复制数据混进新发布。
for name in ('index', 'graph'):
if (directory / name).exists():
shutil.rmtree(directory / name)
return plan_incremental(None, current, services), None
阅读时先找输入由谁提供,再区分纯计算、读取和写入;遇到异常或提前返回,追问调用方会得到什么,以及已有状态是否仍然有效。上面的代码只证明展示范围内的实现,跨模块行为应继续查看调用处和相应测试。
用一个小实验核对设计
建议在独立临时目录和隔离数据中操作,使用虚构的接口、文档与项目名称。
- 先完成小项目索引。
- 修改一个文件观察增量。
- 破坏隔离副本中的基线清单后重试。
**要核对的结果:**基线损坏应转入明确全量计划,旧生效代际继续保持可用。
可把观察记录为下表;没有执行的项写“未执行”,不能用设计预期冒充实测结果。
| 核对项 | 应保留的证据 |
|---|---|
| 使用条件 | 源码版本、配置、输入与项目范围 |
| 正常行为 | 实际返回、来源位置或状态变化 |
| 关键失败分支 | 错误码、可见结果与原状态是否保留 |
| 结论范围 | 已覆盖、未覆盖和需要进一步验证的条件 |
适用边界与继续阅读
本篇以静态源码与项目设计为依据,给出可执行的核对方法,未据此宣称已经完成新的性能或生产环境测试。不同版本的入口、默认值和底层实现可能变化,应先核对当前合同,再把方法用于自己的实际开发项目。
理解这一节后,可以回到专题总导航,沿同一模块继续阅读;顺序学习时使用专栏内的上一篇和下一篇。涉及具体技术选择,应连同本篇的代价与限制一起采用。