社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  Elasticsearch

ElasticSearch 查询的性能与评分问题

马哥Linux运维 • 4 年前 • 659 次点击  

Elasticsearch是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,它天生支持分布式搜索机制,具有搜索、分析、挖掘海量数据的能力,同时还是一个可扩展、近实时的搜索引擎。

本文主要分析ElasticSearch分布式搜索的机制,比较不同的搜索类型,并针对ElasticSearch在搜索过程中存在的一些问题进行分析,同时给出优化方案。

ES分布式搜索过程中,需要将请求分发到所有相关的分片,并将各分片的结果聚合到一起。故搜索过程分为两个阶段:Query和Fetch。假设集群设置为2个主分片、1个副本分片,即每个索引会有4个主副本分片。如图所示:

Query阶段:

1.用户搜索请求到ES集群的某一个节点上,该节点收到用户请求后,以协调节点的身份,在4个分片中随机选择2个分片(构成完整的数据集),将请求分发到对应分片所在的数据节点上。

2.各节点收到请求后,搜索相关分片上的文档,根据本分片上词频及文档频率进行相关性评分计算,同时根据分值进行排序,返回from+size个排序后结果给协调节点,这个结果并不包含全部的文档信息,仅提供足够协调节点对所有结果进行合并重排序的信息。

Fetch 阶段:

协调节点会将Query阶段从每个分片上获取到的排序后的结果重新进行排序及合并,按照集群配置,协调节点总共会收到2*(from+size)个结果,根据重新排序的结果选取from+size个结果,同时根据结果中的文档ID,去对应的分片根据文档ID获取详细文档数据,返回给客户端。

ES的这种查询方式存在两个问题:

1.性能问题

在查询过程中,每个分片需要返回的文档个数为:from+size个,协调节点需要汇总处理number_of_shards*(from+size)个文档,ES为了避免深度分页消耗节点过多内存,默认限定只能查询10000个文档,from+size的数量越大(出现深度分页情况),协调节点处理的数据越多,占用协调节点的内存越大,给协调节点带来较大压力。

2.评分问题

在查询过程中,为文档相关性评分计算都是基于本分片上的词频和文档频率进行的,而协调节点排序是基于各分片评分的结果进行综合排序的,各分片的评分依据不一致,会导致相关性评分偏离的问题;在主分片数量大于1的情况下,如果主分片数里越多,相关性算法会越不准。

针对以上的两个问题,ES提供了一些解决方案:

针对性能问题(深度分页问题),ES有两种用于查询深度分页情况下的查询方法:Search After,使用search after进行查询时,每次查询时会返回当前页的sort值,可以根据sort值查询下一页的数据。使用Search after时多个分页请求需要有相同的查询和排序参数。如果多次分页请求之间,ES进行了refresh操作(即index-buffer中的数据写入到segment,这时index-buffer中的数据将能被查询到),这样会导致排序结果的改变,使得跨页的排序结果不一致。为了避免发生这种情况,可以通过创建PIT的方式,在多次分页请求中保持当前索引的状态,获取PIT时可以指定keep_alive参数,指定PIT存在的时间。

例:POST /my-index-000001/_pit?keep_alive=1m 该API返回一个PIT ID:

第一次查询时,带上PIT ID,同时在sort参数中,需要指定一个唯一的排序字段,这个字段的取值是唯一的(例如doc IDs),如果排序参数中没有这个字段,则有可能会丢失结果或者出现重复结果。

查询返回结果:

返回结果中,会有sort值,在查询下一页时,带上前一页查询返回的sort值,即可实现向下翻页。如果使用了PIT需要一并放在查询参数中。

通过重复这个查询操作即可实现滚动翻页的功能。查询完毕后应该通过DELETE /_pit接口将PIT删除。

Search After存在的局限性:不支持指定页数,即不能设定from参数,只能从第一页开始往后进行;只支持向后翻页,不能跨页访问,只能从当前页一页一页往后翻。Scroll API(当前版本7.11已不再推荐使用Scroll进行深度分页,在这里还是介绍一下这种查询方式)

Scroll查询方式的原理相当于对当前查询创建一个快照,后面的每次查询,都基于第一次查询创建的快照进行。在第一次查询时,需要在请求中指定scroll参数,并设置快照保存的时间。如scroll=1m即该查询的快照将被保持一分钟。

查询将会返回_scroll_id,_scroll_id一般不会发生变化,但是以防万一,下一个查询的请求最好还是使用上一个查询请求返回的_scroll_id。当查询只需要返回全量文档,不考虑文档排序值时,可将sort值指定为_doc,这样可以优化查询的速度。可以通过 POST /_search/scroll 接口更新scroll保存的时间。

因为scroll是对当前查询创建一个副本快照,会占用较大的内存,所以当scroll使用完毕后,需要及时对scroll进行清理。可以使用DELETE /_search/scroll {scroll_id:””}的方式进行。es对scroll的最大数量进行了限制,默认可以创建500个scroll,可以通过search.max_open_scroll_context参数进行设置。

scroll存在的局限性:因为是对查询结果创建快照进行查询,所以当查询期间,如果有新数据写入,这些新增的数据将无法被查询到。

根据以上对不同查询类型的分析,可以得出不同搜索类型的适用场景:

  1. 普通from+size查询方式:适用于只需要获取排序靠前的部分文档。

  2. Search after查询方式:适用于深度分页情况。

  3. Scroll查询方式:适用于单个请求需要获取大量文档的情况。

评分问题

1.当数据量不大时,将主分片的数量设置为1,当数据量很大时,保证文档均匀分布在各个分片上。

2.使用DFS Query Then Fetch,这样在搜索的时候各个分片会收集词频和文档频率,然后根据这些词频和文档频率统一进行相关性评分计算,这样文档的相关性评分最为准确。但是一般不建议使用这种方式,因为会耗费较多的CPU和内存。

作者:中国农业银行研发中心 王灿

Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/110164
 
659 次点击