Skip to content

第 11 章 规模化运营:成本、延迟与容量 ​

一个团队把记忆服务接进内部知识助手,用户从 40 人涨到 400 人。 第一个月账单没变,因为模型调用由固定的几次批处理承担。 第三个月账单涨了六倍,团队的第一反应是「模型涨价了」,实际原因是人均记忆条数从 200 涨到 1400,而每次召回的候选集随之变大,重排器的调用量跟着涨。 第六个月,recall 的 p95 延迟从 300 毫秒变成 2.4 秒,用户开始绕过助手直接搜索。

这条曲线里没有哪个环节突然坏掉。 每个环节都在按自己的斜率增长,只是斜率不同:存储随写入单调增长,成本随候选集增长,延迟随候选集和重排次数增长。 规模化运营的工作,就是把这四条斜率摊开算清楚,而不是等它们交叉。

记忆系统的成本结构和常规 Web 服务很不一样。 常规服务的成本主要随请求数增长,加机器就能摊薄。 记忆系统的成本有四条独立曲线,其中一条(存储)永远单调上升,另一条(写入的模型调用)随内容长度而非请求数增长。 把它们混在一个「QPS × 单次成本」的模型里,算出来的数字一定会错。

本章按「成本结构 → 单文件边界 → 分层与降级 → 延迟预算 → 容量规划 → 运营指标」展开。 所有涉及具体数值的地方都会标明它是假设还是实测,因为这一章最容易出现「看起来很专业的假数字」。

11.1 四项成本结构 ​

记忆系统的支出可以拆成四项,它们各自随不同的变量增长。

成本项随什么增长触发时机可控手段
写入的模型调用写入次数 × 内容长度每次 add 一次单遍抽取批量合并、用规则提取器兜底
嵌入生成写入条数 × 分块数每条记忆一次向量化内置嵌入器、降低维度、延迟向量化
检索的融合计算候选集大小 × 信号数每次 recall缩小 top_k、关掉重排、预算截断
存储累计写入量,只增不减持续分层压缩、导出归档、删除

表 11-1 四项成本的驱动变量与可控手段

写入的模型调用是唯一一项「内容越长越贵」的成本。 单遍 ADD-only 抽取意味着一次写入一次调用,不像两阶段流水线那样可能调用两到三次,但成本仍与输入长度成正比:一段 8000 token 的对话比一段 300 token 的对话贵一个数量级。 可控手段是批量合并(把同一会话的多轮压成一次调用)与规则提取器兜底(无模型时 add() 仍可用)。

嵌入生成随写入条数增长,与内容长度弱相关。 它容易被忽略,因为很多方案里嵌入调用比 LLM 调用便宜,但在「每会话写 20 条」的量级上,嵌入调用次数是 LLM 调用次数的 20 倍。 内置的 128 维确定性嵌入器把这项成本降到零,代价是召回质量(第 8 章已述)。

检索的融合计算随候选集大小增长,这是最容易被低估的一项。 五路信号(语义、BM25、实体图、时间、标签)各自要跑一遍候选集,融合与校准再跑一遍;候选集不是 top_k,而是它的若干倍——召回阶段要取比最终结果更多的候选,否则融合没有意义。 如果配了重排器,还要再加一次模型调用,而它的输入长度等于候选集长度。

存储是唯一单调增长项,且不可逆。 分层压缩能压低单条记忆的字节数,但压不到零;这一项的风险不是账单,而是单文件体积(下一节展开)。

横向对照一个数量级参考:Mem0 官方称每次检索约 6,900 token,全上下文方案 25,000+。 这个对比说明的不是谁更省,而是「把记忆塞进上下文」和「从记忆里召回」在 token 量级上差三倍以上——而 token 是检索成本的主要构成。

11.2 单文件数十 GB 的边界 ​

SQLite 能撑到多大,这个问题问错了。 正确的问题是:在什么写入并发与聚合模式下,单文件开始成为瓶颈。

判据有两个,都不是数据量。

第一个判据是写入并发。 第 9 章已经说过,WAL 让读写互不阻塞,但同一时刻只允许一个写事务,单文件意味着写入串行。 如果某个作用域要承受每秒数十次写入,队列会堆积,flush 的等待时间会线性增长。 这与文件是 1 GB 还是 50 GB 无关——1 GB 的文件在写并发下一样会堵。

第二个判据是跨作用域聚合频率。 物理隔离下,一次跨租户统计是 N 次打开文件。 如果这类查询是常规操作(每小时跑一次全平台报表),文件数量本身就是成本。 同样与单文件体积无关——十万个 10 MB 的文件比一百个 10 GB 的文件更难聚合。

反过来,如果一个部署满足两个条件:写入按作用域天然分散、聚合查询很少,那么单文件涨到数十 GB 也不会成为问题。 SQLite 的 B-tree 深度随数据量对数增长,索引查询的复杂度几乎不变;真正会退化的是全表扫描类操作(例如无索引的 LIKE),而记忆系统的召回走的是索引。

什么时候该换后端:

  • 写入并发超过单写事务的承载,且无法靠分作用域化解
  • 需要多副本同时写入(SQLite 没有跨机复制)
  • 跨作用域聚合成为核心功能而非辅助报表
  • 需要按行级权限做逻辑隔离(此时物理隔离的文件模型反而碍事)

代价要说清楚:换后端的代价不是迁移数据,而是失去「备份即复制文件」这个性质。 一旦换成服务型数据库,备份、恢复、隔离验收三件事都要重建流程,所以换后端的决策点应该尽量往后推。 推的办法是分作用域(已经内建)与导出归档(冷层)。

11.3 冷热分层与降级经济学 ​

分层的目的不是省磁盘,而是让「召回成本」与「记忆价值」对齐。

三层各自的语义:

  • 热层:最近访问、高置信度、被反复召回的条目。全量索引,全信号参与融合。
  • 温层:访问频率下降但仍有价值。保留索引,可能在融合时降权。
  • 冷层:长期未访问。压缩为指针加结构化事实加内容原子,需要时 expand 还原。

关键设计是「降级而非删除」:低价值记忆被降级并压缩,不被静默删除。 代价是存储不会真正下降——压缩只减少字节数,不减少条目数。 收益是可逆性:一条被降级的记忆仍能被 expand 字节级还原,用户不会遇到「这件事系统明明知道却说不知道」。

Free 层的冷层 30 天窗口需要精确理解:它只对 cold 层施加时间窗口,超过 30 天的冷层记忆不再出现在召回结果里,而 hot 与 warm 层无论多老都保留。

这个设计的理由值得写出来:如果对 hot/warm 也施加时间窗口,惩罚的恰好是活跃用户——他们积累的记忆最多,被砍掉的也最多。 只砍冷层,等于只砍「已经不被使用的东西」,对活跃用户的体验没有影响。 反过来说,Free 层对「三个月前那次谈话」这类需求是不足的,因为那条记忆大概率已经沉到冷层。

降级经济学还有一个容易被忽略的对照:软性排序加权不是删除。 截至 2026 年 10 月,Mem0 的记忆衰减用 0.3×–1.5× 的缩放因子(刚访问接近 1.5×,闲置数月接近 0.3× 下限)。 三个限定必须同时成立才准确:仅 Platform 提供、opt-in 且默认关闭、是软排序偏置而非过滤器;官方明确说明不做删除也不做隐藏。 所以它和「分层降级」是两种不同机制:前者调整排序权重,后者改变存储形态与可见窗口。

11.4 延迟预算 ​

一次 recall 的耗时由五段构成,它们的成本量级差异很大。

环节成本来源量级特征优化手段
查询向量化一次嵌入模型调用网络往返为主缓存查询向量、本地嵌入器
BM25 关键词本地 FTS5 查询毫秒级,无网络无需优化
实体图遍历本地 SQLite 多跳与跳数相关限制跳数
时间与标签索引查找毫秒级无需优化
融合与校准纯计算与候选集大小线性缩小候选集
重排(可选)一次模型调用与候选集长度相关关掉、或只对 top-N 重排

表 11-2 一次召回的各环节成本与优化手段

三路本地信号(BM25、图、时间标签)加起来通常在毫秒级,它们不是瓶颈。 真正的成本在两处:查询向量化的一次网络往返,以及重排器的一次模型调用。 后者的输入长度等于候选集长度,所以它同时受「候选集多大」和「候选多长」两个变量影响。 这解释了一个常见现象:把 top_k 从 5 调到 20,延迟涨得比预期多——候选集扩张不只是多返回 15 条,而是让重排器多读 15 条的正文。

渐进式披露是这里最有效的结构性优化。 recall_catalog 只返回标题、一行摘要与展开成本,不返回正文;调用方判断哪几条相关,再用 expand_memory 展开 1–2 条。 注入成本从「O(全部候选)」降到「O(目录) + O(少量正文)」,量级上从 25,000+ token 降到千 token 级。 代价是调用方多了一次决策:它必须能判断「哪条值得展开」。 如果调用方是一个不会做判断的循环,渐进式披露只会增加一次往返而没有收益。

11.5 容量规划 ​

下面这张表给的是估算方法,不是结论。 每个数字都是假设,最后一列标明它对结果的敏感度。

输入假设取值推算敏感性
日活用户1,000—高:直接线性放大写入与召回
人均记忆条数500总量 50 万条中:影响存储与候选集
日均写入/人20 条日增 2 万条高:写入成本的主驱动
日均召回/人30 次日 3 万次召回高:检索成本的主驱动
单条记忆正文200 字约 0.6 KB低:压缩后差异不大
单条记忆含索引与图约 3 KB总量约 1.5 GB中:随分层压缩下降

表 11-3 容量估算的输入假设与敏感度

从这张表能推出三个结论。

存储不是瓶颈。 50 万条记忆约 1.5 GB,这个量级在单文件 SQLite 的舒适区内;真正的压力来自「条数增长不停」,而不是单条体积。 按日增 2 万条算,一年是 730 万条约 22 GB——仍在单文件可承载范围,但已经接近需要分作用域或归档的区间。

写入成本随条数而非用户数增长。 日均 2 万次写入意味着 2 万次抽取调用(除非用规则提取器)加 2 万次嵌入调用。 这是本表最敏感的部分:如果人均日写从 20 条涨到 60 条,写入成本直接三倍,而用户数没变。

召回成本随候选集而非召回次数线性增长。 日 3 万次召回、每次候选集 100 条,就是 300 万条候选参与融合。 如果记忆总量从 50 万涨到 500 万,在 top_k 不变的情况下候选集未必变大(取决于索引),但重排器的输入长度会随正文长度而非条数变化。 这一项敏感度因此是「中」,却最容易被误估。

11.6 运营指标 ​

三类指标各回答一个问题:系统是否健康、运行得怎么样、召回质量是否退化。

doctor 回答「是否健康」。 它检查完整性、计数、磁盘与后端状态。 关键输出是 verify_integrity() 的结果:{valid, total, first_broken_at}。 first_broken_at 有值意味着审计链在某条记录处断裂,需要立刻定位——这通常不是软件 bug,而是有人绕过接口直接改了数据库。

stats 回答「运行得怎么样」。 它给出写入、召回、token 节省等维度的累计统计。 它的主要用途是对账:故障前后的写入计数差、模型调用计数差,能验证第 9 章那份恢复演练清单里的「不重复计费」一条。

recall_health 回答「召回质量是否退化」。 它是只读指标,覆盖的项比较细:调用次数、空结果率、触发相关性下限被剔除的条数、平均返回条数、平均 top1 分、置信带分布、通道命中分布、延迟分位、向量后端成败、胶囊缓存命中率、图边规模,以及最近 20 轮明细。

其中三项最值得设告警:

  • 空结果率上升,说明要么索引坏了,要么查询与内容分布脱节
  • 相关性下限剔除数上升,说明大量候选被判定为不相关——通常是嵌入模型换了或内容语言变了
  • 向量后端成败出现失败,说明已经在走词法回退,此时召回质量下降但不会报错

最后一项是最危险的失效形态。 五路信号带词法回退:嵌入不可用时 BM25 仍然可用,召回不会失败,只是质量下降。 如果没有监控这一项,系统会在「看起来正常」的状态下持续给出较差的召回。

代价是这三类指标都需要有地方跑、有人看。 自托管形态下这是自己的责任;SaaS 形态下平台提供数据,但「什么算异常」仍需自己定义。 指标本身不产生价值,被用来做决策才产生价值。

11.7 本章小结 ​

本章确立三条判断。

第一,记忆系统的成本是四条独立曲线,不是一条。 写入的模型调用随内容长度增长,嵌入随条数增长,检索融合随候选集增长,存储只增不减。 把它们压成一个「QPS × 单价」模型,算出来的数字必然偏离。 这条判断的失效条件是「全部使用内置组件且数据量很小」——此时四项成本都接近零,模型不适用。

第二,单文件的边界由写入并发与跨作用域聚合频率决定,不由数据量决定。 1 GB 的文件在写并发下会堵,50 GB 的文件在分散写入下不会。 换后端的决策点应该尽量往后推,因为换掉的不只是存储,还有「备份即复制文件」这个性质。

第三,分层与降级的目标是让召回成本与记忆价值对齐,而不是省磁盘。 降级而非删除保住了可逆性,Free 层只对冷层施加 30 天窗口,避免惩罚活跃用户。 代价是存储不会真正下降,压缩只减字节不减条目。

本章没有回答的问题是:当记忆总量增长到「召回本身成为主要成本」时,该优化哪一层。 候选集、重排、还是注入格式,三者的收益比取决于具体语料,无法给出通用答案。 这需要在真实数据上做对照实验,而实验方法本身是一个独立问题。


Copyright © 2026 胡景堃(Hu Jingkun). 采用 CC BY-NC-ND 4.0 许可。未经授权禁止传播、复制或用于商业化。