跳到主要内容

智能体系统中的写放大:为什么一次工具调用会命中六个数据库

· 阅读需 11 分钟
Tian Pan
Software Engineer

当智能体决定记住某件事——"用户更喜欢邮件而非Slack"——看起来只是一次写入。实际上,它是六次写入:向量存储中的一个新嵌入、关系数据库中的一行记录、会话缓存中的一个条目、事件日志中的一条记录、审计轨迹中的一个条目,以及上下文存储的一次更新。每一次写入都因为系统的某个部分对数据有合理需求而发生,每一次写入都引入了新的故障点。

这是基础设施层面的写放大,也是生产智能体部署中较为隐蔽的运营危机之一。它不会导致戏剧性的故障,而是导致部分故障:用户偏好在语义上可以被搜索到,但关系查询返回的是过时数据;审计日志显示某个动作已完成,但实际上从未完全提交;缓存是热的,但上下文存储没有更新,因此下一个会话在没有已学习模式的情况下启动。

理解这一切为何发生——以及如何应对——需要借鉴数据库内部知识,而不是智能体框架文档。

为什么智能体同时写入六个地方

分层写入模式不是设计错误。每个存储系统都服务于其他系统无法替代的目的。

关系数据库是权威的真实来源:结构化状态、访问控制、用户配置文件、对话元数据。ACID事务、复杂连接和范围查询都需要它。向量存储支持语义检索——找到与当前上下文相似的记忆,而不是匹配关键词。事件日志提供了所有发生事件的不可变记录,支持时间维度的调试("智能体在下午3点知道什么?")、合规性和重放。会话缓存(Redis或等效系统)的存在是因为关系数据库对于实时对话中的每一步读取来说太慢了。上下文存储将跨会话学习到的模式持久化到上下文窗口之外,以便按需检索。审计轨迹满足合规要求,这些要求通常与操作日志分离。

消除其中任何一个,你就会失去某种独特的能力:删除向量存储,语义搜索就会退化为关键词匹配;删除事件日志,调试长时间运行的智能体就会变成猜测;删除缓存,每个智能体步骤都会产生全表读取延迟。这个架构并不臃肿——它是生产智能体实际需要的最小存储原语集合。

代价是协调复杂性。当所有六次写入必须成功才能保持状态一致时,任何给定操作的完全成功概率大约是各个成功率的乘积。如果每个存储系统的可用性为99.9%,六次同时写入一起成功的概率约为99.4%。每分钟一千次智能体动作,这意味着每分钟六次失败——不是因为有什么问题,而是因为数学在规模上以不同方式叠加。

没人计划的故障模式

大多数智能体基础设施将写入失败视为异常情况。它们不是。

语义漂移发生在向量索引成功但关系数据库事务回滚时。语义搜索现在返回一个在权威存储中不存在的记忆。智能体检索它,对其进行推理,并基于从未提交的数据做出决策。这个失败是静默的——没有抛出异常,没有触发警报。

日志-现实分歧是相反的情况:事件日志记录了一个已完成的动作,但下游关系写入失败了。审计显示用户的偏好已存储。用户数据模型显示它没有。在受监管的环境中,这是一个合规事件,而不仅仅是一个错误。

上下文去同步发生在会话缓存更新但上下文存储没有更新时。智能体在当前会话期间可以访问该偏好,因为缓存是热的。重启时——无论是由于部署、崩溃还是上下文窗口刷新——上下文存储是真实来源。它有旧状态。学习到的行为静默消失。

部分审计缺口在写入到达关系数据库和向量存储但审计轨迹写入超时时出现。从法律角度来看,动作发生了但无法证明。根据你的合规制度,这是代价高昂的那种失败。

模式总是相同的:写入以满足即时请求的方式成功,但使存储层处于不一致状态,只有在后续不相关的操作中才会浮现出来。

真正有效的模式

数据库内部的三种模式以智能体框架文档很少讨论的方式解决了写放大问题。

预写日志

最古老也最可靠的模式:在执行任何状态变化之前,将预期的变化追加到持久的、仅追加的日志中。只有在日志条目持久化之后,才将变化应用于实际数据结构。如果在写入中途发生崩溃,日志条目得以保存,变化可以在重启时重放。

应用于智能体,这意味着将检查点存储视为预写日志。在执行工具调用之前,持久化预期的状态转换。如果智能体在12个步骤中的第7步崩溃,从最后一个检查点重启,而不是从头开始。LangGraph的检查点模型部分实现了这一点——每个图节点在继续之前将智能体状态序列化到检查点后端。

WAL提供的关键属性是崩溃安全的单写入者语义:你总是知道状态转换是否已提交。它不解决的复杂性是多存储协调——日志持久化,但六个下游写入仍然需要协调。

Saga模式

从微服务借鉴的Saga模式是用于多存储协调的适当工具,无需分布式事务。核心思想:将复合写入分解为一系列单独的、可补偿的步骤。每个步骤都有关联的撤销操作。如果第N步失败,执行步骤1到N-1的撤销操作。

对于智能体记忆写入,Saga可能如下所示:

  1. 写入关系数据库 → 失败时:无需撤销,中止
  2. 写入事件日志 → 失败时:删除关系行
  3. 更新向量存储 → 失败时:删除关系行,删除事件日志条目
  4. 更新会话缓存 → 失败时:删除之前的条目(或跳过,视为软失败)
  5. 写入审计轨迹 → 失败时:标记为重试,不回滚之前的步骤

Saga方法迫使你明确决定哪些写入对一致性是必需的(关系数据库、事件日志)与哪些可以被视为具有重试语义的软失败(缓存、审计轨迹)。大多数生产团队通过代码审查非正式地做出这些区分。在Saga定义中明确它们是拥有恢复策略与寄希望于故障罕见之间的区别。

编排变体——一个中央协调者驱动Saga——比编舞变体(每个存储发布触发下一次写入的事件)更容易推理和调试。对于智能体系统,其中智能体框架已经扮演协调者角色,编排变体几乎总是正确的选择。

从同步核心异步扇出

务实的混合方案:识别作为权威真实来源的单一存储系统,使其成为唯一的同步写入。异步扇出到所有其他存储,对失败提供重试和补偿逻辑。

实际上,关系数据库承担这个角色。它提供ACID保证,支持复杂查询,其失败语义被充分理解。写入序列变为:

  1. 同步提交到关系数据库——如果失败,向调用者报告错误
  2. 异步扇出到向量存储、事件日志、缓存、上下文存储、审计轨迹
  3. 跟踪未完成的异步写入;对失败实施重试
  4. 如果异步写入永久失败(在耗尽重试后),标记为手动修复

这创建了有界的不一致窗口:从关系数据库读取始终是权威的;从向量存储、缓存或上下文存储读取可能延迟几毫秒到几秒。对于大多数智能体应用程序,这种权衡是可接受的。智能体检索到稍微过时的嵌入,但基于一致的权威状态行动。

在高负载下,窗口会扩大。在高吞吐量下,异步写入队列可能积压,不一致窗口从毫秒增长到分钟。这需要监控——具体来说,将关系提交和下游存储更新之间的延迟作为一等运营指标跟踪,而不是事后想到的。

统一数据库捷径

解决多存储协调的最简洁方案是不使用多个存储。带有pgvector扩展的PostgreSQL在单一系统中支持向量相似性搜索、结构化查询、ACID事务和仅追加事件表。一次写入,一个事务,一个故障点。

权衡是性能:在十亿向量规模下,专用向量数据库的性能优于pgvector。对于大多数生产部署——其中向量集合在数百万而非数十亿——性能差异无关紧要,而操作简洁性是显著的。

从pgvector开始的团队支付适度的索引性能成本,获得巨大的一致性收益:没有多存储协调问题,因为没有多存储架构。写放大问题消失了。

如果需要,从统一到多语言的迁移路径是明确的:当查询延迟成为瓶颈时,将向量存储提取到专用系统,届时实施异步扇出模式。这比从第一天就设计多存储一致性要容易。

需要监控什么

写放大失败对标准应用程序监控不可见,因为每个单独的写入都以正常错误代码成功或失败。复合失败——写入在存储之间部分成功——在错误率仪表板中没有留下任何痕迹。

有效的监控需要:

每个存储的写入延迟百分位数:跟踪每个单独写入的p50、p95和p99。整体操作的p99由最慢的存储主导。识别哪个存储是尾延迟贡献者是解决它的第一步。

存储之间的写入延迟:对于异步扇出架构,测量关系提交和每个下游存储中相应写入完成之间的时间。对超过你可接受的不一致窗口的延迟设置警报。

部分写入检测:当补偿逻辑触发时,对Saga进行仪表化以发出事件。补偿事件是系统遇到写入排序失败的信号。如果补偿很少,系统是健康的。如果它们很常见,架构有问题。

幂等性验证:多存储架构中的所有写入都应该是幂等的——可以安全地重试而没有副作用。在每次重试之前,验证操作尚未被应用。这可以防止在重试风暴期间重复条目淹没下游存储。

没人明确做出的设计决策

每个智能体系统最终都会发展出一种写放大模式——通常是有机地,因为团队出于合理原因添加存储系统。向量存储被添加用于语义检索。审计轨迹在合规要求出现时被添加。事件日志在调试事件揭示需要重放后被添加。

每次添加都是单独合理的。复合效应——每次智能体动作六次写入,协调复杂性随每个新存储呈O(n²)增长——在导致生产事故之前不会被评估。

这里描述的模式实现起来并不困难。困难在于明确做出决定:哪些写入是同步和权威的,哪些是异步和最终一致的,哪些可以被视为具有重试语义的软失败。大多数团队在数月演化的代码中隐式地做出这些决定。在Saga定义或写入排序规范中明确它们,是将系统优雅处理部分失败与积累静默不一致直到用户投诉将其暴露之间的区别。

写放大问题不会消失。它要么被设计,要么被发现。

References:Let's stay in touch and Follow me for more thoughts and updates