调整近似kNN搜索

edit

调整近似kNN搜索

edit

Elasticsearch 支持 近似 k 近邻搜索,用于高效地找到与查询向量最接近的 k 个向量。由于近似 kNN 搜索与其他查询的工作方式不同,因此在性能方面有一些特殊的考虑。

许多这些建议有助于提高搜索速度。使用近似kNN,索引算法在后台运行搜索以创建向量索引结构。因此,这些相同的建议也有助于提高索引速度。

减少向量内存占用

edit

默认的 element_typefloat。但这个可以在索引期间通过 quantization 自动量化。量化将减少所需的内存 4 倍,但也会降低向量的精度并增加该字段的磁盘使用量(最多增加 25%)。增加的磁盘使用量是 Elasticsearch 存储量化和未量化向量的结果。例如,当量化 40GB 的浮点向量时,将额外存储 10GB 的数据用于量化向量。总磁盘使用量为 50GB,但快速搜索的内存使用量将减少到 10GB。

对于维度大于或等于384float向量,强烈建议使用量化索引。

降低向量维度

edit

kNN搜索的速度与向量维度的数量呈线性关系,因为每次相似度计算都会考虑两个向量中的每个元素。如果可能的话,最好使用维度较低的向量。一些嵌入模型有不同的“尺寸”,既有较低维度也有较高维度的选项。你也可以尝试使用降维技术,如PCA。在尝试不同的方法时,重要的是要衡量对相关性的影响,以确保搜索质量仍然是可以接受的。

_source中排除向量字段

edit

Elasticsearch 存储了在索引时传递的原始 JSON 文档在 _source 字段中。默认情况下,搜索结果中的每个命中都包含完整的文档 _source。当文档包含 高维度的 dense_vector 字段时,_source 可能会非常大且加载成本高。这可能会显著减慢 kNN 搜索的速度。

reindex, update, 和 update by query 操作通常需要 _source 字段。禁用 _source 字段可能会导致这些操作出现意外行为。例如,reindex 可能不会在新索引中实际包含 dense_vector 字段。

您可以通过excludes映射参数禁用将dense_vector字段存储在_source中。这可以防止在搜索期间加载和返回大型向量,并减少索引大小。从_source中省略的向量仍然可以在kNN搜索中使用,因为它依赖于单独的数据结构来执行搜索。在使用excludes参数之前,请确保审查从_source中省略字段的缺点。

另一个选项是使用 合成 _source

确保数据节点有足够的内存

edit

Elasticsearch 使用 HNSW 算法进行近似 kNN 搜索。HNSW 是一种基于图的算法,只有在大多数向量数据保存在内存中时才能高效工作。您应确保数据节点至少有足够的 RAM 来保存向量数据和索引结构。要检查向量数据的大小,您可以使用 分析索引磁盘使用情况 API。作为一个粗略的经验法则,并且假设默认的 HNSW 选项,使用的字节数将为 num_vectors * 4 * (num_dimensions + 12)。当使用 byte element_type 时,所需的空间将更接近 num_vectors * (num_dimensions + 12)。请注意,所需的 RAM 用于文件系统缓存,这与 Java 堆是分开的。

数据节点还应为其他需要RAM的方式留出缓冲区。 例如,您的索引可能还包括文本字段和数值字段,这些字段也可以从使用文件系统缓存中受益。建议使用您的特定数据集运行基准测试,以确保有足够的内存以获得良好的搜索性能。 您可以在这里这里找到一些我们用于夜间基准测试的数据集和配置示例。

预热文件系统缓存

edit

如果运行 Elasticsearch 的机器重新启动,文件系统缓存将会是空的,因此需要一些时间才能将索引的热门区域加载到内存中,以便搜索操作能够快速执行。您可以明确地告诉操作系统应该根据文件扩展名将哪些文件急切地加载到内存中,使用 index.store.preload 设置。

在太多索引或太多文件上急切地将数据加载到文件系统缓存中,如果文件系统缓存不够大以容纳所有数据,将会使搜索变慢。请谨慎使用。

以下文件扩展名用于近似kNN搜索:

  • vecveq 用于向量值
  • vex 用于HNSW图
  • vemvemfvemq 用于元数据

减少索引段的数量

edit

Elasticsearch 分片由段组成,这些段是索引中的内部存储元素。对于近似 kNN 搜索,Elasticsearch 将每个段的向量值存储为单独的 HNSW 图,因此 kNN 搜索必须检查每个段。最近对 kNN 搜索的并行化使其在多个段中搜索的速度大大提高,但如果段较少,kNN 搜索仍然可以快几倍。默认情况下,Elasticsearch 通过后台合并过程定期将较小的段合并为较大的段。如果这还不够,您可以采取明确的步骤来减少索引段的数量。

增加最大段大小

edit

Elasticsearch提供了许多可调设置来控制合并过程。一个重要的设置是index.merge.policy.max_merged_segment。这控制了在合并过程中创建的段的最大大小。通过增加该值,可以减少索引中的段数量。默认值是5GB,但对于较大的维度向量来说,这可能太小了。考虑将此值增加到10GB20GB可以帮助减少段的数量。

在批量索引期间创建大段

edit

一个常见的模式是首先执行初始批量上传,然后使索引可用于搜索。与其强制合并,您可以调整索引设置以鼓励Elasticsearch创建更大的初始段:

  • 确保在大规模上传期间没有搜索操作,并通过将其设置为 index.refresh_interval -1 来禁用。这可以防止刷新操作并避免创建额外的段。
  • 为 Elasticsearch 提供一个大的索引缓冲区,以便在刷新之前可以接受更多文档。默认情况下, indices.memory.index_buffer_size 设置为堆大小的 10%。对于像 32GB 这样的大堆大小,这通常已经足够。为了允许使用完整的索引缓冲区,您还应该增加 index.translog.flush_threshold_size 的限制。

避免在搜索期间进行繁重的索引操作

edit

主动索引文档可能会对近似kNN搜索性能产生负面影响,因为索引线程会从搜索中窃取计算资源。当同时进行索引和搜索时,Elasticsearch也会频繁刷新,从而创建多个小段。这也会损害搜索性能,因为当存在更多段时,近似kNN搜索速度较慢。

如果可能的话,最好在近似kNN搜索期间避免繁重的索引操作。如果你需要重新索引所有数据,可能是因为向量嵌入模型发生了变化,那么最好将新文档重新索引到一个单独的索引中,而不是就地更新它们。这有助于避免上述的减速问题,并防止由于频繁的文档更新而导致的昂贵合并操作。

通过在Linux上使用适度的预读值来避免页面缓存抖动

edit

搜索可能会导致大量的随机读取I/O。当底层块设备具有较高的预读值时,可能会执行大量不必要的读取I/O,特别是在使用内存映射访问文件时(参见存储类型)。

大多数Linux发行版对单个普通设备使用合理的预读值128KiB,然而,当使用软件RAID、LVM或dm-crypt时,生成的块设备(支持Elasticsearch path.data)可能会具有非常大的预读值(在几MiB的范围内)。这通常会导致严重的页面(文件系统)缓存抖动,从而对搜索(或更新)性能产生不利影响。

您可以使用 lsblk -o NAME,RA,MOUNTPOINT,TYPE,SIZE 检查当前的 KiB 值。 请查阅您发行版的文档,了解如何更改此值(例如,使用 udev 规则以在重启后保持,或通过 blockdev --setra 作为临时设置)。我们建议将 readahead 值设置为 128KiB

blockdev 期望值以512字节扇区为单位,而 lsblk 报告的值以 KiB 为单位。例如,要暂时将预读设置为 128KiB 对于 /dev/nvme0n1,请指定 blockdev --setra 256 /dev/nvme0n1