在知识图谱里搜索一个函数名,用户通常期待的是一组相关节点、它们之间的关系,以及能定位到原文的依据。用户没有要求重新审核整张图,但服务为了确认每个对象仍然有效,可能在一次查询里做了接近重新准备资料的工作。
TraceWise 在 2026 年 9 月 10 日的本地对照就遇到了这种情况:图中只有 649 个对象,完整节点服务却花了 77.578 秒。问题不是模型推理慢——这次查询没有模型调用;也不能先归因于九信号排序太复杂,因为绝大部分时间发生在评分之前和之后的图内容读取。
最终采用的办法,是由 Agent Foundation 提前准备并持续维护受治理的检索视图,TraceWise 读取一次、保持原评分逻辑,再在返回前检查视图是否变化。同条件下,这次服务耗时变为 0.281 秒。但读取变快之后,计算并没有消失。后来的批量写入又把问题推到用户面前:图已经写完,来源视图仍未就绪。本文要讨论的是,怎样判断预计算是否值得,以及把等待移到另一个时点后,产品需要承担什么。
先把 77 秒花在哪里说清楚
TraceWise 搜索的对象不只有节点文本。登记到 Foundation 的图对象还关联精确 Evidence revision、依赖约束与治理状态。一个节点即使还在 Neo4j 中,也可能因为必要证据被撤销而暂时不具备当前使用资格。
旧读取路径在产品评分前后各做一次公开图投影读取。它取得当时有效的内容和引用,并刷新支持投影。这样确实能在返回前再次确认资料,但第二次确认仍然执行了昂贵的内容与依赖解释,而不是只检查前一次使用的状态是否改变。
本次固定对照得到以下分解:
| 测量项 | 旧读取 | 维护式视图 |
|---|---|---|
| 完整节点服务 | 77.578 秒 | 0.281 秒 |
| 公开图内容读取 | 2 次,合计 77.414180 秒 | 1 次,0.036868 秒 |
| 返回前的独立版本断言 | 无独立接口 | 1 次,0.015808 秒 |
| 首次视图准备 | 无 | 40.610 秒,单列 |
两种模式使用同一隔离 Neo4j/SQLite 副本、同一安装环境、同一个 EvaluateRetrieval 查询、相同的 328 个节点和 321 条关系,返回 10 条,源图预算为 2000 个对象。九信号权重未变,没有开启 profiler,产物写盘不计入服务计时。
因此,这个问题首先应改的是“为了读一次当前状态,究竟要重复解释多少内容”。继续调整排序权重,解决不了这里的主要开销。
哪些工作适合提前,哪些必须留在查询里
把依赖状态提前算好并不是新鲜思想。Microsoft 对 Materialized View 模式的介绍,强调的是把源数据预先组织成适合查询的形态,同时明确刷新时机、维护成本和一致性要求。这个模式提供了思考方向,但不会自动替我们解决 Evidence 撤销的约束。
下面三个时点描述的是 post22 历史对照实现,用于解释那组数字。
首先,可信宿主显式调用 prepare_retrieval_view。Foundation 完成有界的 Graph、Proposal 与依赖校验,将内容和精确当前依赖持久化到 Evidence SQLite 中。准备需要相应的读取和记录权限,由宿主显式触发,不在普通查询中执行。
其次,发生变更时维护当前资格。Evidence 撤销或被新 revision 取代,会在同一 SQLite 事务中禁用受影响对象;节点失去资格时,它的相邻关系也退出当前检索集合。Graph Proposal 执行则先关闭旧视图,成功写图之后维护之前已经准备的视图。
最后,每次查询仍然校验调用人、登记关系和当前绑定。TraceWise 通过 read_retrieval_view 取得一次当前视图,执行自己的九信号评分,在返回前调用 assert_retrieval_view_current。如果 token 已变,刚算出的结果不再返回;若要重试,应有界地重新读取和计算。
post22 历史机制图:重内容的准备与维护移出普通查询;调用人权限和返回前的当前性检查仍然执行。这里的箭头不表示 Graph 与 SQLite 存在统一事务。
这条分工也决定了改动应该落在哪里。TraceWise 决定召回与排序;对于明确登记的对象,Foundation 掌握版本、撤销和依赖生命周期,所以维护视图与其公开契约落在 Foundation。产品不另起一套证据有效性缓存,也不导入平台内部存储实现。
为什么不直接缓存查询结果
两类缓存回答的问题不同。查询结果缓存保存“这个问题上次返回了什么”,维护式视图保存“这一代图对象中,哪些内容目前可以参加检索”。前者通常还要区分查询参数和排序配置;后者可以供不同查询共享,但必须跟随权威变化失效。
如果只给旧结果设置一个时间期限,在期限内撤销 Evidence,仍可能返回已经不该使用的对象。如果缓存键包含版本,却没有可靠的版本更新和检查路径,键上的版本号也没有解决问题。
维护式视图的 token 标识准备好的某一代状态,并不是授权凭证。持有它不能替代当前身份与作用域检查;不同主体不能仅凭同一个 token 共享使用资格。源图通过受支持的写入路径发生变化时,源标识或 dirty 状态使旧视图不可继续使用。绕过 adapter 的管理员直接改库,不在这个小标识检查的实时探测范围内。
PostgreSQL 的 物化视图说明也明确区分了读取预计算结果的速度与数据是否最新。这里采用的是 SQLite 中自有的受治理视图契约,并没有引入 PostgreSQL 物化视图;共同的设计问题是:谁负责刷新,过期时是否还能读。
0.281 秒之外,还有哪些账要算
第一次准备用了 40.610 秒。这笔成本不能从报告里拿掉,也不能把准备之后的请求叫作冷查询。该对照版本的 Graph 写入后维护仍然是完整有界重建,尚不是按变更对象增量维护任意规模的图。
这意味着方案更容易在“资料相对稳定、查询重复发生”的条件下获得收益。若图频繁写入,重建次数和不可读窗口就可能成为主要成本。是否继续发展增量维护,应该由真实的读写比例、对象规模、更新频率和延迟分布推动,而不是根据这一次查询直接推算容量。
另一笔账是故障处理。Graph 写入成功之后,若视图重建失败,系统保持视图不可读,等待修复;已写入的 Graph 仍然存在。这是跨存储的部分成功状态,不是自动回滚。查询也不会在维护失败时绕回不受同等约束的原始图读取。
读取模式切换后,结果顺序、分数、信号和非易变引用完全一致。含计算时间的支持投影指纹不参与这个相等性比较。这说明本次变化作用于读取机制,而没有通过更换排名结果换取速度;它并不说明检索质量提高。
用一次撤销检查,确认快路径没有绕开变化
性能对照固定之后,在同一隔离副本上撤销一条 Evidence。9 个直接依赖对象与关联边合计 13 个对象退出当前集合,另外 636 个对象保持可用。旧 token 被拒绝,撤销后的产品查询不再返回受影响对象。
关闭并重新打开 Foundation runtime 后,撤销状态、token 与引用集合保持一致,过程中没有重新准备或调用旧整图投影。这为持久维护路径提供了实际检查,但不是操作系统崩溃恢复或并发吞吐实验。
末尾 token 校验也有明确的时间边界:它能发现检查点之前观察到的变化;若撤销在校验之后才提交,已经发出的响应不能被追溯收回。后续写入或其他高风险操作,仍然要执行自身的当前授权和有效性检查。
图已经写完,为什么仍然不能追溯来源
9 月 12 日的 a15/post26 本地运行给维护成本增加了一个具体例子:剩余 11,136 条关系分 348 块执行完成,图达到 12,491 个节点、11,200 条关系,最终指纹与封存计划一致。但同次浏览器记录中的 Evidence 请求返回 409,来源读取仍等待写后视图准备。
这组记录不是前面性能实验的放大版,不能用它推算大图查询耗时。它说明的是另一件事:写入完成与能够沿图追溯来源之间存在一个用户可感知的间隔。是否值得采用预计算,也要看这个间隔能否被接受。
相关工作清单修复保留了这种区别:权威 Evidence 读取不可用时,12,491 个节点的证据状态记为 unknown,缺失 Evidence 修复项为 0。这里的 0 表示没有把未知状态加入缺失修复,并不证明全部节点已有有效证据。若把等待误报成缺失,用户就可能在正确数据上重复补录。
服务化以后,要同时约束刷新和普通查询
a17 的本地集成把节点语义候选交给 Foundation 公开服务,产品保留九信号排序与有界邻域重排。完整统计绑定检索代次,只有代次或维护视图 token 改变时才允许整图刷新;not-ready、stale、unavailable 等状态不会静默换成文本搜索。
这与早期“每次读全图再评分”的接口形态不同,但仍然面对相同的成本问题:普通请求应该做多少工作,哪些变化需要刷新,以及刷新失败时用户能够继续做什么。a17 已有本地代码与包验证,真实共享实例切换尚未完成,本文没有新服务的性能对照。
用读写负载决定,而不是只看最快的一次查询
我会把采用判断放在以下三种工作负载中比较。这是一张分析表,不是项目已测得的容量分级。
| 工作负载 | 预计算可能提供的价值 | 需要重点核算的代价 |
|---|---|---|
| 资料稳定、查询反复发生 | 多次查询共享准备结果 | 首次准备、失效检查与存储 |
| 一次性导入后少量查询 | 可能减少单次读取工作 | 准备成本是否已经超过节省的查询时间 |
| 连续写入、要求立即可读 | 取决于维护粒度与更新策略 | 重建频率、等待窗口、冲突重试与恢复 |
对于一份很小且只查询一次的资料,直接计算可能更合适。对于频繁变更的图,如果每次都需要大范围重建,应先测量写后恢复可读的时间,再决定是否需要增量维护或更小的视图范围。不能只因为查询阶段快,就默认维护成本可以忽略。
对不要求即时当前性的历史报表,显示带时间戳的旧视图可能是合理替代;对已撤销证据参与的正式判断,同样的降级就可能违背任务要求。是否允许旧数据,应由业务后果决定。
这次优化值得留下的判断
77.578 秒与 0.281 秒来自 post22 候选的一次固定顺序、本地 649 对象比较,首次准备 40.610 秒另计。它支持改变重复读取机制,不支持生产延迟分布、普遍加速倍数或检索质量提高。
更大的运行案例补充了原先容易低估的一面:把昂贵工作移出查询,也把等待和失败放到了准备、维护与恢复阶段。判断是否采用维护视图,要一起看查询节省了什么、写后多久可用,以及不可用期间会不会让用户作出错误操作。
下一次评估这样的优化,我会先写下允许的数据新鲜度和用户完成条件,再分别测量准备、读取、写后可读时间与失败恢复。这样,一次快查询才能放回完整任务中判断价值。