从向量搜索到强大的 REST API,Elasticsearch 为开发者提供了最全面的搜索工具集。您可以查阅 Elasticsearch Labs 代码库 中的示例笔记本,体验新功能。您也可以立即开启免费试用或在本地运行 Elasticsearch。
我们已将 simdvec,即 Elasticsearch 原生的单指令多数据 (SIMD) 向量评分引擎,引入了 serverless 环境。在并发负载下,搜索吞吐量几乎翻倍,p99.9 尾部延迟从 237 毫秒降至 30 毫秒。通过让 simdvec 直接访问 blob 缓存的内存映射区域,serverless 现在能够运行与 stateful 版本相同的零拷贝 SIMD 内核,同时保持相同的召回率且零堆开销。由于 serverless 赋予我们对整个存储层的控制权,我们相信它将成为 向量搜索速度最快的地方。以下是我们的实现过程。
Elasticsearch Serverless 基于 Stateless Elasticsearch 构建,这是一种完全解耦的计算和存储架构,其中索引数据存储在远程对象存储中,而搜索节点仅维护本地缓存。为了在这种架构下实现快速的向量搜索,评分引擎需要直接与本地缓存协同工作,而不是先将其复制到堆内存。
Elasticsearch 的 simdvec 是 Elasticsearch 中所有向量距离计算的幕后引擎。它提供经过精心调优的 AVX-512 和 NEON 内核、带有显式预取功能的批量评分,以及堆外内存访问,确保数据直接从存储流向 CPU 寄存器。在 stateful Elasticsearch 上,simdvec 始终拥有直接的“燃料线”:内存映射文件将原生指针直接输入到 SIMD 内在函数中。而在 serverless 环境中,数据虽然就位于 blob 缓存的内存映射区域中,形式也完全正确,但却没有路径将其连接到评分引擎。
现在,我们已经构建了这条路径。simdvec 在 Serverless 上运行,具备与 stateful 版本相同的堆外原生 SIMD 评分能力。而且,由于 serverless 赋予我们对整个存储层的控制权,这仅仅是个开始。
simdvec 的速度源于直接操作堆外内存。它获取一个指向内存映射数据的原生指针,并将其直接传递给 C++ SIMD 内在函数。没有中间拷贝,也没有堆分配。数据直接从存储流向 CPU 寄存器。这比听起来更重要:simdvec 的内核处理向量的速度比数据复制的速度还要快,因此路径中的任何复制都会成为瓶颈,而不是评分本身。
在 stateful Elasticsearch 上,这自然而然地就实现了。Lucene 将索引文件从本地磁盘内存映射,评分器直接从映射区域提取原生指针。正是这条路径,带来了我们已发布的基准测试数据,这也是我们希望引入 serverless 的功能。要了解如何实现,我们首先需要理解 serverless 如何存储和访问数据。
在无状态架构中,所有索引数据的主副本都存储在远程对象存储(如 S3)中。每个搜索节点都维护一个本地缓存(称为 blob 缓存),用于将最近和频繁访问的索引数据部分保留在本地 SSD 上。stateful Elasticsearch 上的 frozen tier 使用相同的架构:Searchable snapshots 由类似的 blob 缓存支持,该缓存将远程存储的区域内存映射到本地磁盘。当搜索命中缓存数据时,它将从快速本地存储中提供服务。当未命中时,blob 缓存会从远程存储中获取数据并进行缓存以供将来查询。
blob 缓存被组织成固定大小的内存映射区域,默认为 16MB。它管理自身的生命周期:跟踪哪些区域正在使用中,当缓存满时应用最近最少使用淘汰策略,并进行引用计数以确保区域在读取时不会被淘汰。这些区域仍然通过操作系统进行内存映射,但 blob 缓存控制哪些区域存在、哪些区域被填充以及何时被回收。在 stateful 环境中,这些决策完全留给操作系统。

此图显示了远程对象存储和 simdvec 之间的数据流。顶部标有“远程对象存储”的框列出了 S3、GCS 和 Azure Blob,并有一个标有“未命中时获取”的箭头指向一个更大的框,标有“Blob 缓存”。Blob 缓存内部有编号为 0-5 的区域以及两个空槽,每个区域 16 MB。区域 0、1、3、4 为绿色并标记为“已缓存”,区域 2 为蓝色并标记为“使用中”,区域 5 为黄色并标记为“正在淘汰”,另有两个灰色框标记为“空”。图例解释了颜色代码。一个标有“直接内存”的向下箭头连接 Blob 缓存到一个深色框,标有“simdvec – 原生 SIMD 评分”。
至关重要的是,由于每个区域都是内存映射的,blob 缓存已经以 simdvec 所需的精确形式保存了向量数据。但在我们建立连接之前,无法访问这些数据。每个向量比较都被复制到一个堆数组中,并交给一个较慢的评分器。没有直接内存指针,没有 SIMD,并且每次调用都会产生垃圾回收压力。
我们引入了一个新的抽象层,允许评分器安全地从底层存储层借用直接内存,时间刚好足够运行 SIMD 计算。如果数据作为直接内存可用,simdvec 的原生内核就会运行。如果不可用(数据尚未缓存或跨越区域边界),评分器会回退到堆拷贝。实际上,回退的情况很少发生。
并排比较图,标题为“之前”和“之后”。“之前”部分显示四个框:蓝色“Stateful – 本地 mmap”、绿色“simdvec – 原生 SIMD ✓”、黄色“Serverless – blob 缓存”和红色“Java 评分器 – 无 SIMD ✗”。箭头表示从 Stateful 到 simdvec 的绿色“直接指针”以及从 Serverless 到 Java 评分器的红色“堆拷贝”,并配有说明文字“两条路径,两种实现”。“之后”部分显示三个框:蓝色“Stateful – 本地 mmap”、黄色“Serverless – blob 缓存”和绿色“simdvec – 原生 SIMD ✓”,并有两个标有“直接”的绿色箭头指向 simdvec,配有说明文字“一个引擎,一个代码路径,所有层。”
这为我们提供了跨所有层的单一评分入口点:
评分器不知道它运行在哪一层,也不需要知道。这也意味着我们不再需要维护单独的评分实现;以前,stateful 有一个快速的原生路径,而其他所有层都有一个较慢的路径。现在,simdvec 的每一次改进都会自动惠及所有层,包括其最强大的功能:批量评分。
单个查询可能需要对数千个候选向量进行评分。simdvec 的批量评分通过多累加器内循环、查询分摊和缓存行预取来批量处理这些向量,当数据超出 CPU 缓存时,其速度比单向量替代方案快 4 倍。
对倒排文件 (IVF) 索引的搜索是批量评分发挥最大作用的地方。查询选择一组候选倒排列表,并扫描量化向量,批量地对它们与查询向量进行评分。在 stateful 环境中,这些向量位于一个连续的内存映射文件中,因此批量评分通过直接的指针算术解析它们,并在单个原生调用中对一批向量进行评分。
在 serverless 环境中,遍历倒排列表可能会跨越 blob 缓存区域边界。我们通过批量访问方法扩展了直接内存抽象,该方法在一次调用中将多个向量偏移量解析到各自的缓存区域。如果批次中的所有向量都已缓存且没有跨越区域边界,评分器将获得一个直接内存切片,并将整个批次传递给 simdvec 的原生批量内核,其预取和流水线处理与 stateful 版本相同。当向量确实跨越边界时,系统会回退到按向量评分:仍然是零拷贝,只是没有批量处理的优势。对于 16MB 区域和 1024 字节向量,这种情况大约每 16,000 个向量发生一次。
simdvec 的批量评分架构是 simdvec 基准测试中强调的关键差异化优势,现在它在 serverless 上运行,并具有与在 stateful 环境中相同的速度特性。那么,它在实践中的表现如何呢?
我们使用一个包含 1800 万个向量的 MSMARCO 数据集进行基准测试,维度为 1024,并使用带有 Better Binary Quantization (BBQ) 1 位量化的 IVF。所有结果均在热 blob 缓存且完整数据集驻留在本地缓存区域的情况下获得,因此我们测量的是评分路径而不是远程获取延迟。
吞吐量。 在并发负载下,搜索吞吐量几乎翻倍,从 398 ops/s 跃升至 739 ops/s。单客户端的增益为 23-39%,但在并发下真正的差异显现出来:改进幅度是 2-3 倍,因为消除堆拷贝消除了以前限制并发评分的 GC 压力和分配争用。

条形图,标题为“搜索吞吐量 — 基线对比零拷贝(中位数 ops/s)”。它比较了八种 knn 配置下基线(堆拷贝)和零拷贝(DirectAccessInput)之间的中位数吞吐量。每组显示一个灰色基线条和一个更高的绿色零拷贝条,上方标有百分比改进。Y 轴显示每秒操作数的中位数吞吐量,范围高达 900。副标题指出百分比标签表示改进。
尾部延迟。 直接内存路径在负载下显著改善了尾部延迟:
p100 从 11.4 秒降至 100 毫秒以下。

折线图,标题为“尾部延迟急剧下降 — knn-10-10 多客户端”。该图比较了在对数刻度下,基线(堆拷贝)和零拷贝(DirectAccessInput)在 p50 到 p100 百分位之间的延迟。红色基线急剧上升,而绿色零拷贝线保持在低位。标签标记了关键点。图注说明了 Rally 基准测试细节和对数刻度。
以前需要数秒才能完成的最差情况下的异常值,现在只需数十毫秒。由堆拷贝引起的排队导致的延迟峰值已不复存在。
召回率保持不变。对相同的向量进行评分,产生相同的结果。而这仅仅是个开始。
达到与 stateful 版本对等是我们的目标。但更有趣的认识是,无状态架构让我们能够做到 stateful 无法做到的事情。
在 stateful 环境中,操作系统控制内存映射文件的行为:哪些页面常驻内存,何时淘汰,以及预读的积极程度。应用程序可以提供提示,但它们适用于整个文件映射,并且内核可能会忽略它们。更糟糕的是,搜索和索引在同一节点上并发发生,因此有利于一种访问模式的提示可能会损害另一种访问模式。实际上,为了平衡不同的需求,您必须采取保守策略。
在 serverless 环境中,有两点根本不同。blob 缓存以完全的应用程序级控制管理自己的内存映射区域。并且 serverless 将索引和搜索分离到专用层:搜索节点从不合并,索引节点从不处理查询。没有冲突的访问模式意味着我们可以积极地提供内存建议。以下是我们正在进行的工作:
blob 缓存赋予我们对内存层次结构的控制级别是操作系统页面缓存无法比拟的。这就是为什么我们认为 serverless 是下一代向量搜索性能工作的最有前景的平台。不仅要与 stateful 匹配,还要超越它。而向量只是一个开始。
simdvec 现在在 Elasticsearch 运行的所有地方(stateful、serverless 和 frozen tier)都以相同的原生 SIMD 评分、相同的批量评分和相同的堆外效率运行。我们构建的抽象是通用目的的,并且已经通过存储链中的每一层进行连接,因此相同的方法将来可以使术语查找、聚合、排序和存储字段检索受益。
Elasticsearch Serverless 是我们对向量搜索性能投入最大的地方。simdvec 的每一次改进、blob 缓存的每一次优化以及存储层面的每一次新改进都将首先在这里实现。如果您正在选择运行向量工作负载的平台,serverless 是一个不断提速的平台。您可以从免费的 Elastic Cloud 试用版 开始。