深入解析 AI 应用中记忆的存储结构、去重策略、版本控制和检索机制,提供 durable knowledge 与 observation 的区分方法。
让 AI 系统记住重要事情的实际架构——从观察到持久知识。

你可以解释一个 AI 应用应该记住什么。
构建一个能可靠地记住这些内容的系统,是完全不同的问题。
在前一篇文章 LLM Memory Is Not Chat History: How to Design Memory That Improves Future Decisions 中,我聚焦于记忆的概念层面:什么值得成为记忆、记忆如何演变、为什么检索是一个决策而非简单的相似性搜索、以及为什么遗忘是生命周期的一部分。
本文深入一层。
假设你正在构建一个 AI 编程助手。在一次 Pull Request 审查中,它发现了一个由非确定性排序引起的分页 bug。这个观察结果最终可能对未来的审查有用。
但现在工程问题开始了。
这个观察结果应该存储在哪里?
应用应该等待 LLM 来解读它吗?
如果同样的经验已经存在会发生什么?
我们如何判断它是一个重复、一个精化、还是一个新版本?
我们如何在不返回过时版本的情况下检索它?
以及如何在不把一个相对简单的 AI 应用变成分布式系统的情况下完成所有这些?
对于我们正在构建的这个架构,你不需要 Kafka、微服务集群或专门的记忆平台。设计良好的模块化单体架构可以走得比你想象的更远。
这就是本文要构建的架构。
我们将设计一个实用的记忆模块,包含规范存储、语义检索、缓存、异步记忆形成、身份解析和生命周期管理。目标不是设计尽可能大的系统。而是设计最小的架构,使其能够表现得像一个严肃的生产级记忆系统,同时保持足够简单以便于构建、运维和演进。
如果前一篇文章是关于记忆意味着什么,那么这一篇就是关于记忆在实际构建时的样子。

架构分为三条互补的路径:写入、读取和生命周期。
写入路径始于应用产生一个将来可能有用的观察结果时。记忆模块将其持久化,并创建一个持久的记忆形成任务,使用户Facing请求得以继续而不必等待记忆处理。然后后台工作进程提取相关信息,可选地使用 LLM 处理非结构化内容,并产生一个候选记忆。身份解析和准入决定该候选记忆是应该创建、强化、精化、修订还是取代现有知识。
读取路径以相反的方向工作。当未来的请求需要记忆时,系统首先使用已知的结构化信号缩小搜索范围,然后在排名之前结合语义检索与修订和生命周期过滤。Redis 加速频繁访问的记忆,而 PostgreSQL 仍是规范的真实来源。
生命周期路径独立地在后台运行,通过强化、整合、过期、取代和删除来维护记忆。
这种分离保持了用户Facing路径的简单性,同时允许记忆独立地形成、演进和被检索。
最后一句话很重要,因为它捕捉到了架构的核心,而无需重复后面的详细章节。
记忆系统的第一个版本应该刻意保持简单。
不是因为记忆很简单,而是因为它的语义已经很复杂了。在工作负载需要之前就增加基础设施复杂性,只会让系统更难理解。
对于模块化单体架构,我会从一个规范的数据库存储开始:
PostgreSQL + pgvector 用于语义检索。
重要的不是数据库本身。而是有一个地方能回答:
存在哪些记忆?哪个版本是当前的?什么证据支持它们?它们是否仍然有效?
Redis 可以加速读取,pgvector 可以帮助找到语义相关的记忆,但都不应该成为应用实际相信什么的权威。
一个有用的心智模型是:
观察(Observation) 是应用遇到的东西。
候选(Candidate) 是看起来值得保存但尚未被承认为持久记忆的信息。
记忆家族(Memory Family) 代表一条不断演变的知识。
记忆修订(Memory Revision) 代表该知识的特定版本。
使用偏移分页。
大数据集使用游标分页。
游标分页必须使用具有稳定次级键的确定性排序。
这不是三条无关的记忆。它们是同一演化中想法的修订版本。
这使得不可变修订很有用。不是覆盖先前的值,而是每次有意义的变更都创建一个新修订,同时记忆家族指向当前版本。
PostgreSQL 是一个好的起点,因为它可以将这个模型的结构化部分保持在一起:
借助 pgvector,语义检索可以与该规范元数据共存,而无需立即引入另一个数据存储。
这不是说 PostgreSQL 是每个记忆系统的最佳数据库。这是一个刻意的起点权衡。
当语料库规模、查询量、延迟或索引要求证明额外的运维复杂性合理时,专门的向量数据库可能变得值得采用。在那之前,将系统保持在一个存储边界内,更容易理解和运维。
原则很简单:
从能够保留记忆系统所需语义的最小存储架构开始。
让工作负载来赢得复杂性。
一旦我们有了一个清晰的事实来源,下一个问题就变得更有趣了:
信息如何在不减缓产生它的应用的情况下进入这个系统?
这就是写入路径开始的地方。
一旦我们有了规范的记忆存储,下一个问题就是:信息如何实际变成记忆?
我首先避免的设计是将记忆形成作为用户Facing请求的一部分:
交互 → LLM 提取 → 嵌入 → 存储 → 响应
这会增加延迟,并使主应用依赖于一个昂贵的、容易失败的后台关注点。
相反,将观察到某事与决定它的含义分开。

重要的决策是先持久化观察结果。
在模块化单体架构中,一个持久的 memory_jobs 表足以创建这种分离:
BEGIN
INSERT observation
INSERT memory_job
COMMIT
请求可以在后台工作进程处理记忆任务时继续。
因为形成任务是持久的,失败的处理可以重试而不会丢失原始观察结果或阻塞用户Facing请求。如果 LLM 提供商暂时不可用,记忆形成可以简单地等待,直到任务可以再次处理。
工作进程不应该仅仅因为 LLM 可用就调用它。
如果结构化事件已经包含我们需要的信息,直接提取即可。
repository = payments-api
component = pagination
issue = unstable-ordering
没有理由花费一次 LLM 调用去重新发现这些字段。
当有价值的信息埋藏在非结构化的对话、审查或工具输出中时,LLM 辅助提取变得有用。
即使如此,模型产生的是一个候选,而不是规范真相。
一旦我们有了候选,需要确定它与现有知识的关系:
- NEW(新)
- DUPLICATE(重复)
- REINFORCEMENT(强化)
- REFINEMENT(精化)
- REVISION(修订)
- SUPERSESSION(取代)
- CONTRADICTION(矛盾)
例如,如果现有记忆说:
Use cursor pagination for large datasets. (大数据集使用游标分页。)
而新的审查发现:
Cursor pagination requires deterministic ordering with a stable secondary key. (游标分页需要具有稳定次级键的确定性排序。)
我们不应该盲目地创建另一条无关的记忆。新信息可能代表了对现有记忆家族的精化或修订。
这就是为什么写入路径不仅仅是:
message → embedding → vector database
嵌入帮助我们找到相关知识。
但它不决定应用应该记住什么。
总体原则很简单:
快速持久化经验。异步解读。慎重地承认知识。
一旦记忆可以在不阻塞应用的情况下形成,我们就需要相反的路径:当未来的请求实际需要记忆时,我们如何检索正确的记忆?
写入记忆只是问题的一半。
当一个新的请求到来时,系统需要回答一个更难的问题:
哪些记忆应该影响这个决策?
直觉上往往直接把所有请求发送给向量索引,然后取最近邻的结果。
这作为候选生成机制是可行的。
但它不应该成为最终的检索策略。
更好的读取路径始于应用已经了解的信息。

如果请求涉及某个特定的代码仓库、组件、环境或实体,这些信号应该在语义检索开始之前就缩小搜索范围。
repository = payments-api
component = pagination
environment = production
memory_type = engineering_rule
然后,语义检索可以在这个相关空间内扩展召回。
这很重要,因为两条记忆在语义上可能很相似,但只有一条真正属于当前问题。
Redis 是加速器,不是真相
Redis 可以位于高频访问记忆的主存储之前。
请求级缓存
↓
Redis
↓
PostgreSQL + pgvector
但缓存永远不应该成为记忆正确性的权威。
面向修订的键可以简化这个问题:
memory:{family_id}:revision:{revision}
memory:{family_id}:current
当新的修订成为当前版本时,指针会改变。旧的不可变修订不需要被覆盖。
这也让应用能够检测到缓存数据是否过期,而不是假设缓存总是最新的。
这里还有一个与前一篇文章的重要区别:
缓存新鲜度和记忆新鲜度是两个不同的问题。
Redis 可以包含最新的修订版本,而该知识可能已经在时间上过时了。
检索是一个排序决策
在找到候选记忆之后,系统仍然需要决定哪些值得产生影响。
有用的信号包括:
时间适用性
不存在一种通用的权重公式能适用于所有应用。
正确的排序策略取决于记忆系统试图优化什么。
重要的架构边界是:语义相似性发现候选;检索层决定哪些候选真正有用。
这个区别让记忆模块专注于返回相关知识。
它也让上下文组装独立于这个模块。记忆系统返回记忆记录。应用决定这些记录如何在最终的模型交互中被使用。
检索不是一个搜索问题。它是一个决策问题。
即使设计良好的检索路径本身也不够。记忆在创建后会继续变化,这意味着系统也需要一种方式来长期维护它们。
记忆必须能自我维护
一个记忆系统不应该在记忆创建后就停止工作。
随着新观察的到来,现有知识可能变得更强、更精确、过时或完全错误。这意味着记忆需要维护路径,就像需要写路径和读路径一样。
相同的后台工作器可以处理这些生命周期操作:

重要的是,这些操作不需要应用请求来同步协调它们。
一个新的事故可能在今天强化一个现有记忆。一个月后,几个事故可能揭示一个更广泛的模式值得整合。最终,一个较早的记忆可能变得无关紧要,或被更新的修订版本取代。
这些是后台维护活动。
整合是将经验转化为知识的地方
假设应用遇到了几个分页故障:
事故 1 → 不稳定的排序
事故 2 → NULL 排序不一致
事故 3 → 不完整的游标状态
单独来看,这些都是观察。
综合起来,它们可能支持一个更具复用性的教训:
游标应该编码数据库使用的完整排序状态。
这条衍生记忆比简单地存储三个越来越相似的事故摘要更有用。
但整合应该保持可追溯。衍生记忆应该保留对支持它的经历的引用。如果后来的证据与结论矛盾,系统应该能够理解该知识从何而来。
这也是为什么递归总结现有总结是有风险的。随着时间推移,信息会越来越远离原始证据。
遗忘和删除是不同的
生命周期维护也需要在遗忘和删除之间划定清晰的边界。
遗忘意味着该记忆不应该再影响正常检索。
删除意味着底层内容应该根据应用的保留策略被实际移除或匿名化。
对于这个架构,删除可以保持刻意简单:
标记为不可用
↓
使 Redis 失效
↓
移除向量表示
↓
删除 / 匿名化主存储内容
在这个阶段不需要分布式删除协调器。
更广泛的原则与我们在整个架构中遵循的原则相同:
保持昂贵的生命周期工作异步,保持主存储状态为权威,让每个转换都是显式的。
至此,这个架构已经足够完整可以构建了。但这引出了一个同样重要的问题:这个简单的架构什么时候会变得不够用?
这个架构在哪里变得不够用
我们构建的架构是刻意简单的。
模块化单体、PostgreSQL + pgvector、Redis 和后台工作器可以让一个记忆系统走得很远。但它不会永远是正确的架构。
重要的问题不是:
"我什么时候应该用微服务?"
而是:
"当前的架构不再能很好地处理什么约束?"
例如,如果记忆语料库和检索工作负载增长到当前配置无法轻松处理的程度,PostgreSQL 可能成为瓶颈。
语义搜索可能最终需要一个专门的向量系统,因为延迟、吞吐量或索引需求已经改变。
后台记忆任务可能增长到足以与应用主要工作负载竞争,使得独立的工作器扩展成为必要。
租户隔离需求可能变得更高,或者不同团队可能需要独立的所有权和部署边界。
多区域需求可能引入另一类关于数据放置、可用性和一致性的约束。
这些是架构演进的合理理由。
但注意它们的共同点:
"系统已经上线生产了,因此它需要微服务。"
它们是可衡量的约束。
一个自然的演进路径可能是:
模块化单体
↓
独立工作器
↓
专门化检索
↓
独立记忆服务
↓
分布式架构
确切路径取决于工作负载。
有些应用可能永远不需要超越第一阶段。其他的可能会随着扩展或所有权边界的出现而逐渐提取各个组件。
这就是为什么我会避免预先设计分布式版本。
每个新服务都会引入另一个网络边界、部署表面、故障模式和运营依赖。如果当前架构能用更简单的基础设施满足需求,那么这种简单性就是一种优势。
目标不是构建尽可能分布式的记忆系统。
而是构建满足需求的最简单的系统,让证据为下一层复杂性辩护。
架构应该变得更分布式,因为测量和所有权边界需要它,而不是因为"生产级"意味着画更多的框。
这个原则远远超出记忆系统。有好的架构不是关于预测每一个未来的问题。
而是留下足够的结构,让下一个问题可以在不需要重建一切的情况下解决。
为 AI 应用构建记忆不需要从分布式平台开始。
一个具有清晰记忆边界的模块化单体、以 PostgreSQL 为事实来源、pgvector 用于语义检索、Redis 用于加速、以及持久化的后台工作器,可以提供一个令人惊讶的坚实基础。
重要的不是组件的数量。
而是背后的决策。
观察应该在被解释之前被持久化。昂贵的记忆形成应该异步进行。候选应该在成为持久记忆之前对照现有知识解决。检索应该结合结构与语义,而不是仅仅信任相似性。记忆应该在创建后继续演化。
第一部分问了一个概念性问题:
AI 应用应该记住什么?
这篇文章回答了架构问题:
我们如何构建一个能可靠地记住它的系统?
答案并非从向量数据库开始。
答案始于一个清晰的记忆模型、一个权威的事实来源,以及足够的结构——让知识得以演进,而不会将每个应用都变成分布式系统。
当工作负载最终提出更高需求时,架构也能随之演进。
从简单开始。让语义变得强健。让工作负载自己去争取复杂性。
📖 Blog by Naresh B. A.
👨💻 后端 & AI 系统工程师 | 分布式系统 · 生产级 ML
🌐 Portfolio: [Naresh B A]
📫 Let's connect on [LinkedIn] | GitHub: [Naresh B A]
感谢你抽出宝贵时间阅读本文。这是我对技术话题的个人见解,非常感谢你的到来。 ❤️
如需进一步处理,你可以考虑屏蔽此人或举报滥用行为。