Elasticsearch向量检索(KNN)千万级耗时长问题分析与优化方案

本文主要是介绍Elasticsearch向量检索(KNN)千万级耗时长问题分析与优化方案,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

最终效果

本文分享,ES千万级向量检索耗时分钟级的慢查询分析方法,并分享优化方案。通过借助内存加速,把查询延迟从分钟级降低到毫秒级别

方案缺点是对服务器内存有比较大的依赖!

主要问题:剔除knn插件,此插件在做ANN检索时,构建查询语句耗时长。

1.背景

1.1 资源背景

es.8.8版本

2个es节点 ; 堆内存31g; 服务器内存资源充足(100+); HDD磁盘

该优化是在forcemerge之后做的工作,如果不做forcemerge,效果会更差。即使做完forcemerge,还是不能满足查询延迟要求。

1.2 数据背景

1799w数据,向量768维度。(不带副本300G 10个分片)

在数据中做ANN检索。检索语句在2.1中。

knn 参数:"num_candidates": 100

耗时长,无响应结果,时间大于1分钟。

  1. 问题定位排查

2.1 检索语句

为了方便查阅,去掉了向量的数据。

GET tilake_vectors-000003/_search?max_concurrent_shard_requests=30&human=true
{"profile": true, "knn": {"field": "content_vector","filter": {"bool": {"must": [{"terms": {"session_id": ["institute"]}},{"term": {"vectorization_method": "title+content"}}]}},"query_vector": [],"k": 10,"num_candidates": 10},"size": 0
}

2.2 检索语句profile结果

{"took": 10006,"timed_out": false,"_shards": {"total": 2,"successful": 2,"skipped": 0,"failed": 0},"hits": {"total": {"value": 10,"relation": "eq"},"max_score": null,"hits": []},"profile": {"shards": [{"id": "[oooFp749QMWECSF0qyMaIA][tilake_vectors-000003][1]","dfs": {"statistics": {"type": "statistics","description": "collect term statistics","time": "6.9micros","time_in_nanos": 6923,"breakdown": {"term_statistics": 0,"collection_statistics": 0,"collection_statistics_count": 0,"create_weight": 4668,"term_statistics_count": 0,"rewrite_count": 0,"create_weight_count": 1,"rewrite": 0}},"knn": [{"query": [{"type": "DocAndScoreQuery","description": "DocAndScore[10]","time": "6.5micros","time_in_nanos": 6587,"breakdown": {"set_min_competitive_score_count": 0,"match_count": 0,"shallow_advance_count": 0,"set_min_competitive_score": 0,"next_doc": 916,"match": 0,"next_doc_count": 10,"score_count": 10,"compute_max_score_count": 0,"compute_max_score": 0,"advance": 524,"advance_count": 1,"count_weight_count": 0,"score": 1228,"build_scorer_count": 2,"create_weight": 1228,"shallow_advance": 0,"count_weight": 0,"create_weight_count": 1,"build_scorer": 2691}}],"rewrite_time": 9320075980,"collector": [{"name": "SimpleTopScoreDocCollector","reason": "search_top_hits","time": "10.4micros","time_in_nanos": 10460}]}]},"searches": [{"query": [{"type": "ConstantScoreQuery","description": "ConstantScore(ScoreAndDocQuery)","time": "49.4micros","time_in_nanos": 49494,"breakdown": {"set_min_competitive_score_count": 0,"match_count": 0,"shallow_advance_count": 0,"set_min_competitive_score": 0,"next_doc": 0,"match": 0,"next_doc_count": 0,"score_count": 0,"compute_max_score_count": 0,"compute_max_score": 0,"advance": 0,"advance_count": 0,"count_weight_count": 1,"score": 0,"build_scorer_count": 0,"create_weight": 46460,"shallow_advance": 0,"count_weight": 3034,"create_weight_count": 1,"build_scorer": 0},"children": [{"type": "KnnScoreDocQuery","description": "ScoreAndDocQuery","time": "2.1micros","time_in_nanos": 2115,"breakdown": {"set_min_competitive_score_count": 0,"match_count": 0,"shallow_advance_count": 0,"set_min_competitive_score": 0,"next_doc": 0,"match": 0,"next_doc_count": 0,"score_count": 0,"compute_max_score_count": 0,"compute_max_score": 0,"advance": 0,"advance_count": 0,"count_weight_count": 1,"score": 0,"build_scorer_count": 0,"create_weight": 754,"shallow_advance": 0,"count_weight": 1361,"create_weight_count": 1,"build_scorer": 0}}]}],"rewrite_time": 22921,"collector": [{"name": "EarlyTerminatingCollector","reason": "search_count","time": "54micros","time_in_nanos": 54011}]}],"aggregations": []},{"id": "[p4MgwgUtTSK6vmkayGHPKg][tilake_vectors-000003][0]","dfs": {"statistics": {"type": "statistics","description": "collect term statistics","time": "13.3micros","time_in_nanos": 13398,"breakdown": {"term_statistics": 0,"collection_statistics": 0,"collection_statistics_count": 0,"create_weight": 7433,"term_statistics_count": 0,"rewrite_count": 0,"create_weight_count": 1,"rewrite": 0}},"knn": [{"query": [{"type": "DocAndScoreQuery","description": "DocAndScore[10]","time": "10.4micros","time_in_nanos": 10449,"breakdown": {"set_min_competitive_score_count": 0,"match_count": 0,"shallow_advance_count": 0,"set_min_competitive_score": 0,"next_doc": 771,"match": 0,"next_doc_count": 10,"score_count": 10,"compute_max_score_count": 0,"compute_max_score": 0,"advance": 1204,"advance_count": 1,"count_weight_count": 0,"score": 1158,"build_scorer_count": 2,"create_weight": 2845,"shallow_advance": 0,"count_weight": 0,"create_weight_count": 1,"build_scorer": 4471}}],"rewrite_time": 10005101571,"collector": [{"name": "SimpleTopScoreDocCollector","reason": "search_top_hits","time": "10.8micros","time_in_nanos": 10837}]}]},"searches": [{"query": [{"type": "ConstantScoreQuery","description": "ConstantScore(ScoreAndDocQuery)","time": "55.7micros","time_in_nanos": 55704,"breakdown": {"set_min_competitive_score_count": 0,"match_count": 0,"shallow_advance_count": 0,"set_min_competitive_score": 0,"next_doc": 0,"match": 0,"next_doc_count": 0,"score_count": 0,"compute_max_score_count": 0,"compute_max_score": 0,"advance": 0,"advance_count": 0,"count_weight_count": 1,"score": 0,"build_scorer_count": 0,"create_weight": 53265,"shallow_advance": 0,"count_weight": 2439,"create_weight_count": 1,"build_scorer": 0},"children": [{"type": "KnnScoreDocQuery","description": "ScoreAndDocQuery","time": "3.2micros","time_in_nanos": 3271,"breakdown": {"set_min_competitive_score_count": 0,"match_count": 0,"shallow_advance_count": 0,"set_min_competitive_score": 0,"next_doc": 0,"match": 0,"next_doc_count": 0,"score_count": 0,"compute_max_score_count": 0,"compute_max_score": 0,"advance": 0,"advance_count": 0,"count_weight_count": 1,"score": 0,"build_scorer_count": 0,"create_weight": 2451,"shallow_advance": 0,"count_weight": 820,"create_weight_count": 1,"build_scorer": 0}}]}],"rewrite_time": 3431,"collector": [{"name": "EarlyTerminatingCollector","reason": "search_count","time": "28.5micros","time_in_nanos": 28514}]}],"aggregations": []}]}
}

2.3 问题发现

其中最耗时的是 rewrite_time, 总共耗时10s,这里的 rewrite阶段耗时为9.3s!

这里反复测试,不同的case,都是类似的现象。

经过排查发现,检索的过程中,只用knn检索,耗时短,加上ANN检索后,耗时变长。

我们使用到了knn插件做加速。通过对比测试,发现这个耗时长和用到的knn插件有关系。在做了修改,剔除掉knn插件后,耗时有好转,2到3s

但是偶尔也会慢7s

这里调整num_candidates 参数从10到100。耗时变长了很多

还是不满足需求,所以继续需要做优化验证。

3. 验证方案

猜想:还是耗时长。尝试使用预加载底层文件的方式,走内存加速。

验证注意事项:全程要考虑查询缓存的影响。对于es条件,相同的条件会命中缓存,在测试过程中,应该通过替换检索条件的内容,来避免查询缓存的影响。

3.1 尝试把es中的向量文件,做预加载

PUT /tilake_test_slow-000003/_settings
{"index": {"store": {"preload": ["vec", "vem", "vex"]}}
}

报错

{"error": {"root_cause": [{"type": "illegal_argument_exception","reason": "Can't update non dynamic settings [[index.store.preload]] for open indices [[tilake_test_slow/B5hiOiOZQwm8rE5yfHOcXw]]"}],"type": "illegal_argument_exception","reason": "Can't update non dynamic settings [[index.store.preload]] for open indices [[tilake_test_slow/B5hiOiOZQwm8rE5yfHOcXw]]"},"status": 400
}

3.2 需要先把索引关闭掉

POST tilake_test_slow-000003/_close

3.3 再执行修改预加载

PUT /tilake_test_slow-000003/_settings
{"index": {"store": {"preload": ["vec", "vem", "vex"]}}
}

3.4 再打开索引

POST tilake_test_slow-000003/_open

3.5 验证效果平均耗时100ms!

3.6 为什么预加载的是这几个文件?

不妨看看es 底层的文件找到对应索引的uuid

GET _cat/indices/tilake_test_slow-000003?v

根据id,可以进到es的底层存储目录中(es data目录,这里给一个示例:elasticsearch/data/indices/B5hiOiOZQwm8rE5yfHOcXw/1/index看到如下底层文件。其中有三个是hnswVectors相关的文件。es向量检索用的是hnsw算法,es存储向量就和几个相关。这块要熟悉lucene,知道这种底层文件都是什么用的三个是es8.x之后出现的内容)

3.7 内存的前后变化

操作前

操作后看到 buff/cache 增加4G

该设置并不会立即将所有相关文件加载到内存,而是在需要时才会进行预加载。因此,你可能需要在执行查询或重启节点后,才能看到内存使用的变化。

3.8 需要多少内存

以一个分片为例,该分片总大小为30G,以下是该分片全部的底层文件。其中和向量相关的文件有5.5G 。假设这些都需要加载到内存中,则为实际索引大小的五分之一。以我们的数据为例,我们累计1790W数据, 磁盘存储350G,不带副本。按照1:5的比例估算内存,则需要70G的内存空间为佳。

-rw-rw-r-- 1     68 Aug 21 14:53 _20b_0.doc
-rw-rw-r-- 1     68 Aug 21 14:53 _20b_0.pos
-rw-rw-r-- 1    29M Aug 21 14:53 _20b_0.tim
-rw-rw-r-- 1   283K Aug 21 14:53 _20b_0.tip
-rw-rw-r-- 1    265 Aug 21 14:53 _20b_0.tmd
-rw-rw-r-- 1   2.3M Aug 21 14:53 _20b_ES87BloomFilter_0.bfi
-rw-rw-r-- 1     99 Aug 21 14:53 _20b_ES87BloomFilter_0.bfm
-rw-rw-r-- 1    15K Aug 21 14:51 _20b.fdm
-rw-rw-r-- 1    24G Aug 21 14:51 _20b.fdt
-rw-rw-r-- 1   1.3M Aug 21 14:51 _20b.fdx
-rw-rw-r-- 1   4.7K Aug 21 16:35 _20b.fnm
-rw-rw-r-- 1    16M Aug 21 14:53 _20b.kdd
-rw-rw-r-- 1    45K Aug 21 14:53 _20b.kdi
-rw-rw-r-- 1    260 Aug 21 14:53 _20b.kdm
-rw-rw-r-- 1   280M Aug 21 14:53 _20b_Lucene90_0.doc
-rw-rw-r-- 1   222M Aug 21 14:53 _20b_Lucene90_0.dvd
-rw-rw-r-- 1   4.4K Aug 21 14:53 _20b_Lucene90_0.dvm
-rw-rw-r-- 1   421M Aug 21 14:53 _20b_Lucene90_0.pos
-rw-rw-r-- 1   204M Aug 21 14:53 _20b_Lucene90_0.tim
-rw-rw-r-- 1   2.7M Aug 21 14:53 _20b_Lucene90_0.tip
-rw-rw-r-- 1   2.2K Aug 21 14:53 _20b_Lucene90_0.tmd
-rw-rw-r-- 1   5.4G Aug 21 16:35 _20b_Lucene95HnswVectorsFormat_0.vec
-rw-rw-r-- 1   129K Aug 21 16:35 _20b_Lucene95HnswVectorsFormat_0.vem
-rw-rw-r-- 1    79M Aug 21 16:35 _20b_Lucene95HnswVectorsFormat_0.vex
-rw-rw-r-- 1   7.7M Aug 21 14:52 _20b.nvd
-rw-rw-r-- 1    247 Aug 21 14:52 _20b.nvm
-rw-rw-r-- 1    815 Aug 21 16:35 _20b.si
-rw-rw-r-- 1    395 Aug 22 20:00 segments_6p
-rw-rw-r-- 1      0 Aug 21 11:30 write.lock

4. 注意事项

4.1 工作原理

当你配置 index.store.preload 时,Elasticsearch 会使用底层操作系统的文件系统缓存(通常是页缓存)将指定类型的文件(如 .vec、.vem、.vex)预加载到内存中。文件系统缓存是操作系统层面的一种机制,用于将磁盘上的数据读取到内存中,从而加快后续的访问速度。通过 preload,这些文件在第一次访问时会直接从内存而不是从磁盘读取,减少了磁盘I/O的延迟。

在 preload 配置下,Elasticsearch 会在查询时或者索引段被加载时,将指定文件类型的数据主动读取到内存中。这使得后续查询能够更快地访问这些数据,因为它们已经驻留在内存中,而无需进行磁盘读取。Elasticsearch 会利用这些预加载的数据来提高检索性能,尤其是在频繁访问的场景下,可以显著降低查询延迟。

4.2 内存限制

内存资源:由于 preload 会增加内存使用量,因此在配置时需要确保系统有足够的内存资源,以免影响整体性能。注意这些文件是被加载到了os cache上。占用的是服务器的内存。

也就是说,假如服务器的内存资源不够,此优化带来的收益是很小的,甚至有副作用。因为内存不足,可能会导致内存被不停的换入换出。

4.3 es 不要部署在容器中

es部署在容器中,会有各种限制,可能会看不到效果。主要是内存的影响。

持久化的东西放在容器中,会有很大的性能损失。

4.4 可能会存在第一次查询很慢的情况

预加载触发

  • 第一次对索引进行查询时,如果预加载的文件(如 .vec、.vem、.vex 文件)尚未被加载到内存中,Elasticsearch 需要从磁盘读取这些文件,并将它们加载到内存中。这会导致首次查询的响应时间较长,因为磁盘 I/O 操作通常比内存访问慢得多。

操作系统缓存

  • 即使你已经设置了 index.store.preload,实际的预加载动作是在首次访问时才会触发。如果系统刚刚启动或这些文件之前没有被访问过,那么操作系统还没有将它们缓存到内存中,因此第一次查询需要进行磁盘读取。

段文件加载

  • 当新的段文件生成(例如在写入数据或合并段时),这些新的段文件同样需要在首次访问时加载到内存中,这也可能导致第一次查询变慢。

解决方法,使用滚动索引,索引小一些。然后可以做forcemerge+触发查询加载。

5. 新的探索方向

以内存为代价的优化方案不具有扩展性。 如果需要将索引五分之一的数据都放在内存上,这需要非常大的开销。

应该探索其他的优化方案

5.1 探索1: 和向量相关的文件,是不是都需要预加载。做测试验证。

结论1走文件预加载,不做merge也可以生效。影响最大的是,预加载的时间长,体现在open索引的时候耗时就长。

结论2vec 文件占用空间最大,但是vec是必须加载的,否则无法提速,验证如下:

把上述5个索引,放在一个索引中,然后测试检索,耗时为73s。

注意本次未做merge,共373个segment。索引共有10个shard,如果做merge,应该是10个segment

5.1.1 其中 vem最小,先尝试只预加载这个文件

POST tilake_test_final/_closePUT /tilake_test_final/_settings
{"index": {"store": {"preload": ["vem"]}}
}POST tilake_test_final/_open

37s 时间有减半。(注意这里,需要换一个向量,否则会走到缓存上)

避免随机性,又换一个向量。45s。

5.1.2 再加入vex文件

POST tilake_test_final/_closePUT /tilake_test_final/_settings
{"index": {"store": {"preload": ["vem","vex"]}}
}POST tilake_test_final/_open

时间变短到19s

再测一组

5.1.3 加入vec文件

POST tilake_test_final/_closePUT /tilake_test_final/_settings
{"index": {"store": {"preload": ["vem","vex","vec"]}}
}
POST tilake_test_final/_open

查询验证,已经到了毫秒级,269毫秒。

再验证一组

这篇关于Elasticsearch向量检索(KNN)千万级耗时长问题分析与优化方案的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1108950

相关文章

Vue3 的 shallowRef 和 shallowReactive:优化性能

大家对 Vue3 的 ref 和 reactive 都很熟悉,那么对 shallowRef 和 shallowReactive 是否了解呢? 在编程和数据结构中,“shallow”(浅层)通常指对数据结构的最外层进行操作,而不递归地处理其内部或嵌套的数据。这种处理方式关注的是数据结构的第一层属性或元素,而忽略更深层次的嵌套内容。 1. 浅层与深层的对比 1.1 浅层(Shallow) 定义

基于MySQL Binlog的Elasticsearch数据同步实践

一、为什么要做 随着马蜂窝的逐渐发展,我们的业务数据越来越多,单纯使用 MySQL 已经不能满足我们的数据查询需求,例如对于商品、订单等数据的多维度检索。 使用 Elasticsearch 存储业务数据可以很好的解决我们业务中的搜索需求。而数据进行异构存储后,随之而来的就是数据同步的问题。 二、现有方法及问题 对于数据同步,我们目前的解决方案是建立数据中间表。把需要检索的业务数据,统一放到一张M

无人叉车3d激光slam多房间建图定位异常处理方案-墙体画线地图切分方案

墙体画线地图切分方案 针对问题:墙体两侧特征混淆误匹配,导致建图和定位偏差,表现为过门跳变、外月台走歪等 ·解决思路:预期的根治方案IGICP需要较长时间完成上线,先使用切分地图的工程化方案,即墙体两侧切分为不同地图,在某一侧只使用该侧地图进行定位 方案思路 切分原理:切分地图基于关键帧位置,而非点云。 理论基础:光照是直线的,一帧点云必定只能照射到墙的一侧,无法同时照到两侧实践考虑:关

HDFS—存储优化(纠删码)

纠删码原理 HDFS 默认情况下,一个文件有3个副本,这样提高了数据的可靠性,但也带来了2倍的冗余开销。 Hadoop3.x 引入了纠删码,采用计算的方式,可以节省约50%左右的存储空间。 此种方式节约了空间,但是会增加 cpu 的计算。 纠删码策略是给具体一个路径设置。所有往此路径下存储的文件,都会执行此策略。 默认只开启对 RS-6-3-1024k

好题——hdu2522(小数问题:求1/n的第一个循环节)

好喜欢这题,第一次做小数问题,一开始真心没思路,然后参考了网上的一些资料。 知识点***********************************无限不循环小数即无理数,不能写作两整数之比*****************************(一开始没想到,小学没学好) 此题1/n肯定是一个有限循环小数,了解这些后就能做此题了。 按照除法的机制,用一个函数表示出来就可以了,代码如下

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

性能分析之MySQL索引实战案例

文章目录 一、前言二、准备三、MySQL索引优化四、MySQL 索引知识回顾五、总结 一、前言 在上一讲性能工具之 JProfiler 简单登录案例分析实战中已经发现SQL没有建立索引问题,本文将一起从代码层去分析为什么没有建立索引? 开源ERP项目地址:https://gitee.com/jishenghua/JSH_ERP 二、准备 打开IDEA找到登录请求资源路径位置

使用opencv优化图片(画面变清晰)

文章目录 需求影响照片清晰度的因素 实现降噪测试代码 锐化空间锐化Unsharp Masking频率域锐化对比测试 对比度增强常用算法对比测试 需求 对图像进行优化,使其看起来更清晰,同时保持尺寸不变,通常涉及到图像处理技术如锐化、降噪、对比度增强等 影响照片清晰度的因素 影响照片清晰度的因素有很多,主要可以从以下几个方面来分析 1. 拍摄设备 相机传感器:相机传

高效+灵活,万博智云全球发布AWS无代理跨云容灾方案!

摘要 近日,万博智云推出了基于AWS的无代理跨云容灾解决方案,并与拉丁美洲,中东,亚洲的合作伙伴面向全球开展了联合发布。这一方案以AWS应用环境为基础,将HyperBDR平台的高效、灵活和成本效益优势与无代理功能相结合,为全球企业带来实现了更便捷、经济的数据保护。 一、全球联合发布 9月2日,万博智云CEO Michael Wong在线上平台发布AWS无代理跨云容灾解决方案的阐述视频,介绍了

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi