Perplexity 工程团队 2026 年 9 月 14 日公开搜索热存储替换复盘:把 DynamoDB 换成自研 CobbleDB 后批读中位延迟从 31.4ms 降到 5.60ms、p99 从 123ms 降到 24.2ms,内部成本模型在每一档承诺用量下至少低 20%,前置部署 Forward Deployed Engineer(FDE)可复用其 50KB 记录口径与自建判据
2026 年 9 月 14 日 Perplexity 公开复盘:热存储由 DynamoDB 换为自研 CobbleDB,批读中位延迟降 5 倍、成本低 20%。
这条复盘正落在系统集成与成本这一层。前置部署 Forward Deployed Engineer(FDE)最常被问倒的就是这里:客户现场的读路径延迟与云账单同时超标时,是继续调托管服务参数,还是把这一层自己拿回来。
前置部署 Forward Deployed Engineer(FDE)交付的检索与存储层,往往要同时扛住企业 RAG enterprise RAG、企业检索与智能体多轮取数的批量读;Perplexity 给出的量化前后对比、架构取舍与自述局限,可以直接当作存储层选型与验收的参照基线。
结论先给:当单次调用要扇出上百个 key、单条记录几十 KB、读量远大于写量时,托管键值库 key-value store 的按字节计费与不可调度的内部副本策略会同时变成瓶颈;而自建热存储换来的是运维责任与一致性弱化,这三点原文都写明了。
一、三个可以直接写进验收表的数字
迁移热存储后,批量读中位延迟从 31.4ms 降到 5.60ms,p90 从 56.7ms 降到 9.77ms,p99 从 123ms 降到 24.2ms。
三个分位的改善倍数落在 5.08 倍到 5.80 倍之间,中位与尾部同时改善,不是拿尾部换中位。
成本侧,按内部对存储量、写容量单元与读容量单元三项的估算,在评估过的每一档承诺用量下,CobbleDB 都比 DynamoDB 至少低 20%。
原文明确说这 20% 没有计入压缩可能带来的备份节省,因此真实节省「可能更高」;它没有披露绝对金额,也没有披露承诺用量的档位划分。
前置部署 Forward Deployed Engineer(FDE)把它写进验收表时可以照抄:中位、p90 与 p99 三个分位一起测,避免只测平均值把尾部风险盖掉。
二、改造前是什么样:一张 prepared-pages 表加三条约束
改造前的链路是:一张专门的 prepared-pages 表存放页面切块与向量嵌入 vector embedding 供查询期读取,整页正文与元数据另存一处;处理链路把每个处理好的页面直接写进这张热表。
约束一,按字节计费。一次 Search API 请求携带 100 至 120 个页面 key,取数流程把请求拆成每批 10 至 20 个 key,单条记录平均约 50KB。反复读这些记录消耗的容量很大,而持续爬取、更换切块方法或更换嵌入模型又会产生大批写;成本同时取决于记录大小与读出频次,不只是 API 调用次数。
约束二,托管服务不可调。原文说他们需要的操作很窄——「给我一批页面标识,快速返回准备好的内容」——但他们需要决定哪个分区落在哪台机器、给它的缓存分多少内存、哪个副本承接请求。一次未命中缓存的读、一次跨可用区跳转、一个慢副本,就足以拖慢整批读。
约束三,处理与热存储耦合。一次大规模重跑会变成一波单条热存储写入,处理侧必须迁就热存储的吞吐上限与重试策略;原文的原话是他们无法「在 1 至 2 天内」对整库跑一次 MapReduce 去试新的切块方法、换嵌入模型或加字段,因为不存在一条独立、可被下游按自己节奏批量投递的页面更新流。
前置部署 FDE 在客户现场遇到的通常是同一张账单:托管库按吞吐与字节计费,而业务只看到端到端变慢,两头对不上。
三、新架构拆成三件:Pillar 管持久状态、Lorry 管批量投递、CobbleDB 管热读
Pillar 是文档状态与发布层,底层用 YTsaurus,按页面组件(元数据、切块、嵌入)分表族存放并记录版本,让多套表示可以并存而不互相覆盖,模型换代时不用推翻旧表示。因为 YTsaurus 落在便宜的机械硬盘上,能保存的文档量比原先放在 DynamoDB 里多得多。
Pillar 还维护「子集」——按策略划分的页面分组,例如新鲜页面与高价值页面;子集归属决定哪些更新需要向下游发布,因为 CobbleDB 用的是更贵的 NVMe,只导出真正要用于搜索结果的那部分文档。
Lorry 是一个无状态服务,只做一件事:从按分区对齐的持久队列里读导出记录,累积成每个分区的批文件,写到 S3,并把批次标识登记到 CobbleDB,相当于把数据面(S3)与控制面拆开。
CobbleDB 是只服务批量查询的热存储,key 是哈希后的页面标识,value 是预处理好的切块与逐块向量嵌入。每个分区在三台不同节点上各有一个副本;节点内用 RocksDB 作嵌入式存储引擎,命中走内存、未命中读本地 NVMe,节点内通过 RocksDB 的 MultiGet 批量读接口一次取多个 key。
查询路由器本身无状态,按哈希把 key 映射到分区并并行下发;它优先把请求发给同可用区 availability zone 的节点以省掉跨区跳转,若某个节点响应慢,就同时向另一台节点上的分区副本发一次读做对冲,用来改善尾延迟 tail latency。
为了省开销,这套热存储刻意不做事务、不做同步副本:允许文档写入到可读之间有短暂窗口,也允许一个副本比另一个副本多落后一些。原文的判断是,砍掉工作流用不到的能力可以直接降低系统开销与成本。
前置部署 Forward Deployed Engineer(FDE)从这套拆分里能直接抄走的是「三件事分开」这个原则:持久状态、批量投递、低延迟读各用一种存储,不硬塞进一个数据库接口。
四、改造代价与执行方式:40000 行 Rust、两名工程师、两个月
CobbleDB 核心数据库本身约 40000 行 Rust;迁移之外还要在生产规模下验证正确性与故障行为、把 CobbleDB 同时接进处理链路与服务链路、并安全切换线上流量。
团队规模是两名人类工程师加数百个 AI 编码智能体,周期两个月。智能体承担的是持续巡检与收尾:审代码与基础设施改动、找出容易被漏掉的阻塞点(不安全的恢复假设、过期的构建引用、只在运行时才会失败的配置)、准备修复与测试与监控与运维文档、盯住 CI 与评审与基础设施门禁、在发布与备份恢复演练后把观测到的系统状态与成功判据对齐。
责任边界也写明了:架构由工程师定,关键改动由工程师评审,生产操作明确保留在人类手里。
压测口径:两套系统都在约 200k rps 下被测,另外做过 500k rps 的负载测试,且没有观察到退化。
前置部署 FDE 更该关注的是这条责任边界:智能体可以承担巡检、测试与文档,架构决策与生产授权仍要留在人手里。
五、原文自己承认的局限与本次未覆盖的场景
第一,这是观测型的前后对比,不是受控实验。原文写明两套系统是在不同时间服务真实生产流量,而不是在同一时刻用同一批请求跑对照;主结果因此只能算「迁移前后」的观测值。
第二,为了补上受控条件,团队另跑了合成基准:批次同样取 10 至 15 个 key,value 覆盖 100B 到 100KiB。也就是说受控证据只覆盖了这个批次区间与这个载荷区间。
第三,两处口径不能混用:验证段描述的生产读取是每批约 10 至 15 个 key,而成本段描述的是每次 Search API 请求携带 100 至 120 个 key、拆成每批 10 至 20 个 key。拿 10 至 15 去套按字节计费的模型,会低估读放大。
第四,20% 这个结论来自内部估算模型,只覆盖存储量、写容量单元与读容量单元,原文自己说未计入压缩带来的备份节省。本文要补一句原文没有展开的成本项:自建之后节点生命周期、备份校验与分区再平衡都要自己承担,这部分人力成本没有出现在 20% 里。
未覆盖的场景还包括:文中没有给出回滚路径;没有量化副本各自按节奏追赶所造成的一致性窗口究竟多长;也没有回答以单 key 点查为主、记录只有几百字节的工作负载是否还值得自建——原文反而强调 CobbleDB 不是通用数据库 general-purpose database,而是为「重复批量读预处理记录、上游异步投递」这一种工作负载专门造的。
另外,原文说计划在近期开源 CobbleDB,但本次写作时尚未给出仓库地址与许可证,落地前需要再确认。
前置部署 FDE 做方案评审时,把「观测型前后对比不是受控实验」这句话写进风险页,可以省掉后面一轮扯皮。
六、谁该行动、怎么做、什么条件下别做
该行动的是这样一类角色。前置部署 Forward Deployed Engineer(FDE)与数据工程 data engineering 团队在下面三个条件同时成立时才值得动存储层:单次请求扇出上百个 key、单条记录几十 KB、读量远大于写量。
第一步不是选型,是量三个数:单次调用平均扇出多少个 key;单条记录的平均字节数;读容量与写容量的比例。这三个数决定答案——扇出越大,存储侧的 p99 越会被放大成 API 侧的 p50,按字节计费的账也越难看。
第二步,把验收表从「单 key 读延迟」改成「批量读的 p50、p90 与 p99」,并在合成基线上用每批 10 至 15 个 key、value 覆盖 100B 到 100KiB 复现一次;这是原文唯一给出的受控口径,可以直接照抄。
第三步,如果决定自建,把三条实现要求写进设计:节点内用批量读接口(原文用 RocksDB 的 MultiGet)而不是循环单 key 取;路由优先同可用区、并对慢副本发对冲读;显式接受「写入到可读有短窗口、副本异步追赶」,用这个让步换掉事务与同步副本的开销。
第四步,如果不自建,就先算读放大再谈优化:单条约 50KB、每次请求 100 至 120 个 key,按托管服务 4KB 一个读单位的口径自行换算后,再看账单结构对不对得上。这一段换算是本文的推导,不是原文数字。
退出与回滚:原文没有给回滚方案,这一点必须自己设计。可以参考它自身架构里天然可重放的那一环——持久状态先落地,再由无状态服务攒成分区对齐的批文件写到对象存储,热存储按批异步摄取;只要这条批量导出通道保留着,切换流量时就能按分区分批退回,而不是靠一次整库重灌。这条同样是本文的推论。
不该行动的场景也要说清:单 key 点查为主、记录只有几百字节、写读比例均衡的业务,既享受不到批量读的优化,也不会撞上按字节计费的天花板,自建只会换来一堆运维责任。
原始来源:
- https://www.perplexity.ai/hub/blog/cobbledb
- https://web.archive.org/web/20260917234933/https://www.perplexity.ai/hub/blog/cobbledb
- https://www.infoq.com/news/2026/09/cobbledb-perplexity/
评论(0)
- 暂无评论。