← 返回文章列表

把图查询从 77 秒降下来以后,等待去了哪里?

从一次维护视图对照到批量写入后的来源等待,讨论预计算如何改变读取成本,以及何时值得承担刷新与恢复代价。

返回 TraceWise 项目总览

在知识图谱里搜索一个函数名,用户通常期待的是一组相关节点、它们之间的关系,以及能定位到原文的依据。用户没有要求重新审核整张图,但服务为了确认每个对象仍然有效,可能在一次查询里做了接近重新准备资料的工作。

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 已变,刚算出的结果不再返回;若要重试,应有界地重新读取和计算。

Foundation 提前准备维护视图,TraceWise 读取并评分后检查视图标识

查看原图

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 秒另计。它支持改变重复读取机制,不支持生产延迟分布、普遍加速倍数或检索质量提高。

更大的运行案例补充了原先容易低估的一面:把昂贵工作移出查询,也把等待和失败放到了准备、维护与恢复阶段。判断是否采用维护视图,要一起看查询节省了什么、写后多久可用,以及不可用期间会不会让用户作出错误操作。

下一次评估这样的优化,我会先写下允许的数据新鲜度和用户完成条件,再分别测量准备、读取、写后可读时间与失败恢复。这样,一次快查询才能放回完整任务中判断价值。

← 返回文章列表