开发环境流畅的AI pipeline在生产环境频繁出问题,文章深入剖析实时AI在规模部署中的核心挑战与应对思路。
我们很高兴你在这里。你可以期待 TNS 最佳内容在周一至周五送达,让你随时掌握新闻动态并保持最佳状态。
查收你的邮箱中的确认邮件,你可以调整偏好设置,甚至加入其他群组。
在你最喜欢的社交媒体平台上关注 TNS。
成为 LinkedIn 上的 TNS 粉丝。
在等待第一封 TNS 时事通讯时,看看最新的精选和热门故事。
大规模实时 AI 比看起来要难得多。在开发中运行流畅的 pipeline 一到生产环境就频频出问题。人们总是很容易把问题全怪到模型头上。但像延迟上升和精度下降这类问题,通常可以追溯到数据 pipeline 本身。
我和同事 Tim Koopmans 最近讨论了大规模实时 AI 通常会出现什么问题。Tim 分享了一些来之不易的经验教训之后,我们也聊了如何避免自己踩进这些坑——包括哪些实践和基础设施选择能帮助你规避这些问题。你可以观看完整视频,也可以阅读下面的关键要点。
Tim 是在构建一个基于 ML 的金融交易应用时,经过反复挫折才学到了以下实时 AI 性能教训。
很多时候,延迟在测试中看起来没问题,然后在真实并发负载下 P99 延迟就会飙升。例如,当 Tim 的应用接近每秒约 74 万次操作时,P99 延迟骤升至 3 秒。

Tim 解释说:"我一直责怪模型太慢,但结果表明模型没问题。问题出在特征查询上。每一次推理调用只做了少量读取,但这些读取在负载下会在写入后面排队。平均延迟看起来还行,但 P99 尾延迟实在无法接受。
"尾延迟不是你能够修复的 bug,它是架构的一种属性。"
一旦达到高度并发的写入吞吐量,就会出现锁竞争——而这会影响尾延迟。到这个时候,重试、更大的缓存和连接池调优都帮不上忙。正如 Tim 所说:"尾延迟不是你能够修复的 bug,它是架构的一种属性。比如,如果你的存储引擎恰好在错误的时刻产生 GC 暂停,你就必然会遇到延迟峰值,无论如何都没用。"
Tim 应用中的罪魁祸首实际上是压力下的 Postgres:"它不是慢数据库,但在这个特定场景下,它被要求做的事情太多了。"
如果你注意到模型本身无法解释的神秘精度下降,特征新鲜度可能是问题所在。

对 Tim 来说,这个问题特别令人沮丧。用户画像(钱包地址)陈旧度超过五分钟 SLA 目标好几个小时,向量嵌入也在变得陈旧,而离线评估指标始终看起来很好。正如 Tim 所说:"你遇到了这种令人抓狂的情况——离线评估指标看起来很棒,但一旦把它和在线数据混在一起,性能就一塌糊涂。"
"离线评估指标看起来很棒,但一旦把它和在线数据混在一起,性能就一塌糊涂。"
当模型在生产环境中时,它开始做出不一致的调用。在花了似乎很长时间调试模型之后,结果发现模型本身没问题。问题在于模型是基于旧数据在做决策——本质上就是垃圾进、垃圾出。
无论向量数据库厂商如何暗示,"设置好就不管"对嵌入来说并不是一个现实的策略。每次重新嵌入都会让索引稍微腐烂一点,不管你有没有注意到它在发生。

Tim 也遇到了这个问题。每次改进模型时他都会重新嵌入内容,而索引质量在每次传递中都会稍微腐烂一点。有一次,他注意到召回率(近似搜索实际找到的真实最佳匹配占比)降到了可怜的 42%——同时查询延迟也暴涨了。他解释说:"HNSW 图在承受变更时会退化。讨厌的是,你直到发现结果被污染了才会意识到这个问题。"
他建议其他人把向量索引当作你维护任何其他数据库索引一样来对待。它需要你运营的任何其他东西同等的关心、关爱和关注。这意味着:
另一个问题是资源竞争——比如训练和服务争用同一台硬件。Tim 只有一台机器身兼两职。由于所有东西都运行在同一套基础设施上,GPU、RAM 和 CPU 都在争夺资源。顺带一提:很多人没有意识到向量搜索是 CPU 成本而不是内存成本,因为你是在遍历图而不是单纯存储向量。

解决方法每个分布式系统人员都已经知道了:你必须把它们分开。这不过是良好的工程原则:把你的写路径和读路径分开,如果条件允许的话把训练和服务分开。"
要认识到重训练不是可选的,也不是免费的。每次模型切换都需要过渡时间。
Tim 解释说,有一个可疑的窗口期——旧模型仍在提供陈旧预测,而新模型还没有预热好。对于数据库来说,这可能意味着新的访问模式、缓存未命中、冷读取或请求队列堆积。当你注意到数据在漂移,或者用户行为在变化,你三个月前训练的模型越来越差——这就是该重训练的信号。
这迟早会发生,所以要做好规划。Tim 自己的做法是蓝绿部署、金丝雀发布、用不同的名称并行运行旧模型和新模型,并在应用层而不是一次性完成实际切换。如果你在 Tripadvisor 的规模——有 1 亿个 ML 模型——你可以想象这个过程会复杂得多。
这些问题往往会相互叠加并滚雪球。延迟导致陈旧,陈旧降低准确性,准确性下降触发重训练,重训练导致竞争,而竞争又让延迟更恶化。它可能造成 Tim 所称的"doom loop"。
以下是避免这种 doom loop 的一些建议。
要对监控有强迫症。特别关注新鲜度和积压情况,因为不断增长的积压最终会推高尾延迟。还要关注索引健康状况,因为召回率就是在那里腐烂的。而且要做超出稳态的负载测试,因为你无法真正预测某些奇怪的因素组合何时会导致流量激增。
这解决了前面两个问题:导致尾延迟的写风暴,以及训练和服务共享同一套基础设施。一个能处理并发写入且能正确隔离工作负载的数据库可以同时吸收这两个问题。
例如,使用 ScyllaDB,写路径是无锁的并且支持多写入器。这意味着每个节点以主动-主动方式接收写入,没有行会被锁定。结果,一波并发写入不会像在基于单写入器假设构建的数据库那样备份到队列中。
在此之上,我们称之为"工作负载优先级"的做法控制着工作负载如何竞争系统资源。这确保了延迟敏感的查询是快的,即使有其他重型工作负载在同一集群上运行。这样,重训练任务或回填不会从服务于实时推理的任何东西那里偷走资源。
为了解决向量索引问题,把索引分开,而不是把它附加到与核心数据库相同的进程上。例如,ScyllaDB Vector Search 写入首先进入核心数据库,索引作为自己的服务异步从该数据构建。

如果索引跟不上写入速率(无论是重新嵌入传递还是在相似度函数更改后进行完整重建),它会落后——但它永远不会丢失一次写入,核心数据库也不会受影响。即使向量存储宕机,嵌入仍然保存在核心数据库中。而且因为 ANN 查询是 CPU 重型的,将它们放在单独的服务上意味着它们不会与核心数据库争夺写入所需的相同 CPU 周期。
在十亿向量基准测试中,这种分离架构在并发 300 的情况下将 P99 延迟保持在 10 毫秒以下。它以中等召回目标处理了每秒约 15 万次 ANN 查询。要意识到更高的召回会带来延迟和吞吐量的权衡取舍,一定要提前测试以评估你自己的实际效果会有何不同。
这取决于你的基础设施能否在不需要手忙脚乱的情况下吸收写入压力或流量形态的突然变化。存储引擎的架构在这里非常重要。
例如,ScyllaDB 构建在 LSM-tree 上,它可以容忍这种写入压力而不是在压力下性能下降。通过我们称之为"tablets"的弹性扩展,可以在几分钟而不是几小时内将集群扩展约 10 倍。这意味着,如果模型发布在一夜之间改变了你的访问模式,或者你需要在大型重训练之前吸收一个回填,你不需要等待数小时的重分片作业。
AI 有很多方面确实是全新的,但上述基础设施问题通常并不是。
"实时 AI 实际上是一个化了装的分布式系统问题。"
Tim 提到特征存储可能是最早的原始用例:在有人称之为 AI 之前多年,同样的高写入吞吐量、低延迟工作。实时 AI 实际上是一个化了装的分布式系统问题。一旦你理解了这一点,你就可以为之做设计,这样你就不会在这些并不那么新的挑战面前措手不及。