跳到主要内容

你的向量数据库也有热点 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 范围或聚类质心进行分片,热门嵌入通常会聚集在一个或两个分片上。按平均流量进行的容量规划会使这些分片的配置不足。
加载中…
References:Let's stay in touch and Follow me for more thoughts and updates