跳到主要内容

你的向量数据库也有热点 Key:为什么 ANN 索引在生产成本上“撒了谎”

· 阅读需 12 分钟
Tian Pan
Software Engineer

你团队选择的向量索引是在一个生产环境中根本不存在的工作负载上进行基准测试的。每一个公开的 ANN(近似最近邻)基准测试 —— VIBE、ann-benchmarks、数据库厂商落地页上的对比表 —— 都是从语料库中均匀采样查询的,因此每个邻居查找的成本大致相同,每个分片承受的负载也大致相等。真实的检索流量并非如此。它呈现出齐普夫分布(Zipfian):极小部分的查询(今日新闻、趋势产品、循环的支持意图、客服团队整天收到的那几百个问题)命中的一小部分嵌入,其频率比中位数高出百倍。基准测试显示 HNSW 在 50ms p99 下的召回率为 0.97。而生产环境则显示一个分片正在熔化,其余的却闲得发慌。

这种不匹配并不是调优问题。而是向量检索继承了所有其他数据库工作负载的访问倾斜特性,而该领域标准化的索引在设计时并未考虑到这种特性。你的 KV 存储免费获得的缓存层 —— 预热你最常读取的行的操作系统页面缓存(OS page cache),针对热点 Key 的 LRU —— 在 ANN 中并不存在,因为图是按图结构顺序遍历的,而不是按访问顺序。热门嵌入在内存中依然是“冷的”,因为搜索算法的遍历模式在页面缓存看来是随机的,而你的“热门”集群位于单个分片上,其 CPU 运行火热,而集群的其他部分却在闲置。

这篇文章探讨了 ANN 索引评估方式与实际使用方式之间的差距,以及在这一差距演变成生产事故(postmortem)之前,必须在生产中落地的运维规范。

基准测试是均匀的;而你的流量不是

当研究人员比较 HNSW、IVF、ScaNN、DiskANN 或最新的图变体时,他们从数据集中(或从具有相同分布的预留测试集中)均匀采样查询。召回率、延迟和吞吐量被报告为该样本的平均值。VIBE 论文在采用现代嵌入和分布外查询集方面做了细致的努力,但即使是那项工作也只是在比较索引质量,而不是倾斜流量下的运维表现。

生产检索至少有三个基准测试所抹去的倾斜来源:

  • 时间倾斜。 新闻应用的今日查询是关于今日新闻的。代码搜索工具在某个刚刚发布重大版本的框架上出现峰值。支持机器人在针对相同的 200 个意图嵌入时产生了 70% 的访问量。
  • 租户倾斜。 在多租户 SaaS 中,少数大客户贡献了不成比例的流量,他们的语料库子集将点击集中在索引的一小部分上。
  • 图拓扑倾斜。 在像 HNSW 这样的图索引内部,一小组高出度的“中心(hub)”节点几乎在每次查询中都会被遍历。这是数据结构本身固有的,而非你的工作负载导致的 —— 并且它决定了索引的哪些部分主导了缓存压力。

前两个是流量的属性。第三个是算法的属性。两者都会与缓存层级发生交互,而这些在基准测试中都不会体现。

缓存行为是隐藏的成本模型

Flat 索引按顺序扫描向量,因此 CPU 预取器有效且缓存未命中是可预测的。图索引 —— HNSW 及其系列磁盘增强变体如 DiskANN —— 按照取决于查询的顺序遍历指针,而这种顺序对预取器来说看起来是随机的。图遍历期间的距离计算会引起频繁的缓存未命中,像 VSAG 这样的现代系统已经开始专门引入软件预取,正是因为这种访问模式对硬件缓存极不友好。

现在在此基础上叠加倾斜的流量。热门嵌入 —— 那些每个热门查询都会触达的嵌入 —— 应该留在 L2/L3 或者至少在操作系统页面缓存中。但它们没有,因为图是按距离近邻程度而非访问频率对节点进行排序的,而保存热门向量的图部分与很少被访问的邻居交织在一起。结果是:在严重倾斜的情况下,你的“内存”索引在每一步跳转都在支付 RAM 获取成本,而你的“磁盘增强”索引则在一次又一次地为相同的热门向量支付 SSD 获取成本。

这就是为什么大规模运行 DiskANN 的团队如此关注入口点邻域的缓存:入口点是最密集的中心节点,将它们固定在缓存中是健康的 p99 与排队等待之间的区别。这也是为什么分层架构开始胜出的原因。Milvus 2.6 交付了明确的热/冷分离;围绕 S3 Vectors 的生产模式是将冷嵌入保存在对象存储中,并将热门嵌入提升到 OpenSearch HNSW。通常只有不到 10% 的数据占据了 80% 以上的查询流量,对这些层级进行相同处理是在为你无法有效利用的内存买单。

你所缺失的单个嵌入访问直方图

每个关于热点 Key 的运维故事都始于一个团队希望自己早点构建的仪表板。对于关系型数据库,它是慢查询日志加上按行或分区的访问频率视图。对于 KV 存储,它是热点分片指标。对于向量存储,等效的是单个嵌入(或单个集群)的访问频率直方图,而几乎没有团队会在出问题之前构建它。

缺失的原因在于索引抽象隐藏了它。你查询一个向量并得到一个最近邻列表。系统并不会自然地告诉你哪些嵌入被返回的次数比中位数高出 100 倍。你必须对搜索调用进行检测以记录邻居 ID(如果担心 PII,则记录哈希分桶),在滚动窗口内进行聚合,并将分布可视化。一旦你这样做,三件事就会变得清晰:

  • 长尾热点。 一小组嵌入主导了结果分布。有时它们是高质量的标准答案,有时它们是内容 Bug —— 一个比预期标准答案稍微接近热门查询的副本。
  • 分片不平衡。 如果索引按 ID 范围或聚类质心进行分片,热门嵌入通常会聚集在一个或两个分片上。按平均流量进行的容量规划会使这些分片的配置不足。
  • 缓存拟合信号。 长尾热点的大小告诉你,是采用按查询结果缓存、分层热索引,还是全面的内存升级才是正确的干预措施。

一个缺乏这种视图的团队在操作向量存储时,就像没人会那样操作数据库一样:对哪些 Key 承载了负载一无所知。

语义缓存是 ANN 等效的页面缓存

语义缓存——以查询嵌入(Embedding)而非字面查询字符串为键来缓存响应——是 ANN 本身未内置的缓存层。当工作负载呈现倾斜分布时,其经济效益非常惊人:生产团队报告 40–70% 的查询是早期查询的语义重复(词汇不同,意图相同);调优良好的系统在第一周内的缓存命中率可达 45–65%,随着缓存覆盖范围的扩大,这一比例会攀升至 60–80%。该领域的学术系统 RAGCache 通过缓存检索知识的 KV 状态,并使用考虑了顺序、频率、大小和最近性的前缀感知 GDSF 替换策略,在 RAG 工作负载上实现了 1.2–4 倍更低的 TTFT(首字延迟)。

理解语义缓存的正确方式是:它是嵌入领域缺失的操作系统页面缓存。其有趣的设计选择包括:

  • 以嵌入的邻域而非字面查询为键。 如果你以精确查询字符串为键,几乎会错过所有命中。如果你以嵌入为键,并接受相似度阈值内的任何缓存响应,你就能吸收大部分重复流量。
  • 根据领域设置阈值。 客户支持意图可以容忍激进的阈值,因为标准答案是相同的。医疗或法律检索则几乎不能容忍任何偏差。
  • 租户级缓存。 单个租户内部的倾斜程度比整个数据集更剧烈;全局缓存大多只针对最大的租户进行训练。
  • 将其视为索引而非边车。 一个不跟踪相对于底层索引召回率的缓存会发生偏移。RAGCache 式的方法是针对基准真实检索(Ground Truth Retrieval)测量命中率,而非仅针对自身。

语义缓存并不会取代索引。它吸收了热门查询类别,从而让索引能够将时间花在长尾查询上——而长尾查询才是索引在基准测试中实际应对的工作负载。

针对热门类别的 p99 进行容量规划

向量索引的默认容量规划方案通常是根据工作集大小分配内存,并根据平均 QPS 分配 CPU。但在倾斜负载下,这两个数字都会产生误导。工作集必须容纳热尾部(Hot Tail),并为图遍历开销预留空间;而“平均 QPS”则会将空闲分片与饱和分片混为一谈。正确的模型包含三个指标:

  • 热尾部工作集。 指前 10% 的查询所命中的嵌入所占用的字节数,加上连接它们的图结构,以及即使在冷尾部查询中也会被触及的高度数枢纽(High-degree Hubs)的余量。
  • 热门查询类别的 p99。 在报告 p99 之前,先按查询类别(热门、中频、长尾)切分你的延迟直方图。综合数字会掩盖具体哪个类别在受苦,而热门类别正是用户感知的关键——那些是人们每天都会进行的查询。
  • 分片饱和度,而非集群平均值。 热点键意味着一个或两个分片的 CPU 或内存会达到饱和,而集群平均利用率看起来却很正常。请针对最饱和的分片进行扩容。

针对平均值进行规划的团队购买的容量并没有投入到负载所在的地方。而针对热门类别的 p99 进行规划,并在每次索引重建后重新检查分片平衡的团队,能够保持用户实际体验中的延迟曲线平稳。

为你拥有的工作负载而设计

架构上的转变陈述起来很简单,执行起来却不容易:停止像操作均匀的数值工作负载那样操作你的向量索引,开始像操作它实际表现出的那种倾斜且对缓存敏感的数据库那样去操作它。具体而言:

  • 在调优索引之前,先构建每个嵌入的访问直方图。 在你不知道哪 5% 的向量承载了 80% 的流量之前,去调优 ef_construction 或 nlist 是在优化错误的参数。
  • 当热/冷比例明显时,采用分层存储。 内存中的 HNSW 热层加上对象存储或 DiskANN 中的冷层,比针对热门子集均匀地为整个索引配置资源更便宜、更快。
  • 在索引之上层叠语义缓存。 它是吸收重复流量最廉价的方式,也是索引本身无法免费获得的唯一方式。
  • 按查询类别而非平均值规划容量。 热门类别的 p99 是交付给客户并形成产品感知的真实延迟。
  • 在索引重建后重新平衡。 大批量重新嵌入后的质心偏移(Centroid Drift)可能会将热点键移动到不同的分片,导致之前的规划在不知不觉中过时。

更深层的意识是,ANN 基准测试测量的是算法的属性;而生产成本是工作负载的属性。从排行榜上挑选索引的团队做出的是算法选择,而这必须通过运维规范来支付代价——包括直方图、分层、容量模型——这些都是基准测试未测量的。这种规范正是检索系统与众不同的地方:一套系统的成本模型与其流量相匹配,而另一套系统则在永远无法战胜热点键工作负载的图遍历中,悄无声息地燃烧着 RAM、SSD 和 CPU。

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