Elasticsearch学习-搜索调优

Elasticsearch学习-搜索调优

系列文章目录

  1. Elasticsearch学习-关于倒排索引、DocValues、FieldData和全局序号
  2. Elasticsearch学习-搜索调优
  3. Elasticsearch学习-Doc原理

0x01 摘要

本文会讲讲es中的4种搜索模式以及该如何选择,还会说一些常用的搜索调优选项。
本文基于ES-2.3.3

0x02 search_type

执行分布式搜索时可以执行不同的执行路径。需要将分布式搜索操作分散到所有相关分片,然后收集所有结果。

分布式搜索中的两个最重要问题:

  • 从每个分片中搜索多少结果
  • 每个分片独立,所以在特定分片上执行查询时不会考虑其他分片的TF。那么如果需要准确的排序就需要从所有分片搜集TF最后再聚合算出全局TF,利用全局TF来在每个分片上执行查询

Elasticsearch非常灵活,允许控制基于每个搜索请求执行的搜索类型。可以通过在查询字符串中设置search_type参数。

在讲解各个搜索选项前,我们先简单说下ES搜索中的相似度算法。

ES 2.x中默认使用的相似度算法叫 TF(词频)/IDF(逆向)文档频率算法:

  • 词频:计算某个词在当前被查询文档里的**某个字段(field)**中出现的频率。出现的频率越高,文档越相关。
  • 逆向文档频率:计算某个词在索引内所有文档中出现的百分数。文档出现的频率越高,它的权重就越低。

由于性能原因, ES 不会计算索引内(统筹所有跨节点分片)所有文档的 IDF ,而是每个分片根据该分片内的所有文档计算一个本地 IDF 。

因为文档一般都是均匀分布存储的,也就是说任意两个分片的 IDF 是基本相同的。但如果有 5 个 foo 文档存于分片 1 ,而第 6 个文档存于分片 2 ,在这种场景下, foo 在一个分片里非常普通(所以不那么重要),但是在另一个分片里非常出现很少(所以会显得更重要)。这些 IDF 之间的差异会导致不正确的结果。

但在实际应用中,这并不是一个问题,本地和全局的 IDF 的差异会随着索引里文档数的增多渐渐消失,在真实世界的数据量下,局部的 IDF 会被迅速均化,所以上述问题并不是相关度被破坏所导致的,而是由于数据太少。

2.1 QUERY_THEN_FETCH(查询后取回,默认选择)

思想:先返回足够、尽量少的信息用来打分排序,然后只返回指定数量的数据content字段
查询执行在所有分片上,但是只有部分必要的信息被返回(而不是整个doc内容)。随后结果被普通排序以及根据size做rank排序,在此基础上只需要去相关的那几个分片请求数据的整个content字段。返回的hits的数量的依据的是用户查询时指定的 size 字段,所以只有这些内容会被获取。

  1. 查询阶段:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  2. 取回阶段
    在这里插入图片描述
    在这里插入图片描述

当要搜索的index有大量分片(不是副本数)的时候,用这种方式搜索特别快。

2.2 QUERY_AND_FETCH(查询并且取回,Deprecated)

思想:直接查询所有相关的分片来返回数据,每个分片都查出指定 size 的数据全部返回给调用者,再做合并、排序、返回。查询、传输的数据量 = shard数 * size

注意:一般不要手动指定为此模式。
已经在 ES 2.0.0 版本中被删除,详情点击这里

query_and_fetch(查询并且取回) 搜索类型将查询和取回阶段合并成一个步骤。这是一个内部优化选项,当搜索请求的目标只是一个分片时可以使用,例如指定了 routing(路由选择) 值时。 虽然你可以手动选择使用这个搜索类型,但是这么做基本上不会有什么效果。

2.3 DFS_QUERY_THEN_FETCH

思想:和QUERY_THEN_FETCH思想相同,但DFS_QUERY_THEN_FETCH有一个预查询阶段来从每个相关分片中获取TF词频计算全局TF以获得更准确的相关性评分

注意:一般不要手动指定为此模式,不要在生产环境上使用 DFS_QUERY_THEN_FETCH 。完全没有必要。只要有足够的数据就能保证词频是均匀分布的。没有理由给每个查询额外加上 DFS 这步

dfs 搜索类型有一个预查询的阶段,它会从全部相关的分片里取回IDF来计算全局的IDF。

有时无法按相关度排序并提供简短的重现步骤: 用户索引了一些文档,运行一个简单的查询,然后发现明显低相关度的结果出现在高相关度结果之上。

可以设想,我们在两个主分片上创建了索引和总共 10 个文档,其中 6 个文档有单词 foo 。可能是分片 1 有其中 3 个 foo 文档,而分片 2 有其中另外 3 个文档,换句话说,所有文档是均匀分布存储的。

2.4 DFS_QUERY_AND_FETCH(Deprecated)

思想:和QUERY_AND_FETCH思想相同,但DFS_QUERY_AND_FETCH有一个预查询阶段来从每个相关分片中获取TF词频计算全局TF以获得更准确的相关性评分

注意:一般不要手动指定为此模式
已经在 ES 2.0.0 版本中被删除,详情点击这里

2.5 其他

countscan 已经被废弃了。

0x03 搜索选项

合理配置一些query-string参数能够对搜索效率有很大提升。

3.1 preference

preference 参数允许你控制使用哪个分片或节点来处理搜索请求。她接受如下一些参数 _primary , _primary_first , _local , _only_node:xyz ,_prefer_node:xyz 和 _shards:2,3 。这些参数在文档搜索偏好(search preference)里有详细描述。

然而通常最有用的值是一些随机字符串,它们可以避免结果震荡问题(the bouncing results problem)。

3.2 bouncing results(结果震荡)

想像一下,你正在按照 timestamp 字段来对你的结果排序,并且有两个document有相同的timestamp。由于搜索请求是在所有有效的分片副本间轮询的,这两个document可能在原始分片里是一种顺序,在副本分片里是另一种顺序。(有点类似算法不稳定性)

这就是被称为结果震荡(bouncing results)的问题:用户每次刷新页面,结果顺序会发生变化。避免这个问题方法是对于同一个用户总是使用同一个分片。方法就是使用一个随机字符串例如用户的会话ID(session ID)来设置 preference 参数。

3.3 timeout

通常,协调节点会等待接收所有分片的回答。如果有一个节点遇到问题,它会拖慢整个搜索请求。
timeout 参数告诉协调节点最多等待多久,就可以放弃等待而将已有结果返回。返回部分结果总比什么都没有好。

搜索请求的返回将会指出这个搜索是否超时,以及有多少分片成功答复了:

...
"timed_out":     true,  (1)
"_shards": {
   "total":      5,
   "successful": 4,
   "failed":     1     (2)
}, ...

(1) 搜索请求超时。

(2) 五个分片中有一个没在超时时间内答复。

如果一个分片的所有副本都因为其他原因失败了——也许是因为硬件故障——这个也同样会反映在该答复的_shards 部分里。

3.4 routing

在路由值那节里,我们解释了如何在建立索引时提供一个自定义的 routing 参数(默认是使用_id字段进行映射)来保证所有相关的document(如属于单个用户的document)被存放在一个单独的分片中。在搜索时,你可以指定一个或多个routing 值来限制只搜索那些分片而不是搜索index里的全部分片:

这个技术在设计非常大的搜索系统时就会派上用场。

0x04 聚合查询调优

参考:

以下转自作者kennywu76
在这里插入图片描述

  • global ordinals方式
    Terms aggregation默认的计算方式并非直观感觉上的先查询,然后在查询结果上直接做聚台。ES假定用户需要聚台的数据集是海量的,如果将查询结果全部读取回来放到内存里计算;内存消耗会非常大。因此ES利用了一种叫做global ordinals的数据结构来对聚合的字段来做bucket分配,这个ordinals用有序的数值来代表字段里唯一的一 个字符串,因此为每个ordinals值分配一个bucke就等同于为每个唯一的term分配了 bucket。之后遍历查询结果的时候,可以将结果映射到各个bucket里,就可以很快的统计出每个bucket埋的文档数了。

    这种计算方式主要开销在构建global ordinals和分配bucket上,如果索引包含的原始文档非常多,查询结果包含的文档也很多,那么默认的这种计算方式是内存消耗最小、速度最快的。

  • execution_hint:map
    如果指定execution_hint:map则会 更改聚合执行的方式,这种方式不需要构造global ordinals ,而是直接将查询结果拿回来在内存里构造一个map来计算,因此在查询结果集很小的情况下会显著的比global ordinals快。

    要注意的是这中间有一个平衡点,当结果集大到一定程度的时候. map的内存开销带来的代价可能就抵消了构造global ordinals的开销,从而比global ordinals更慢,所以需要根据实际情况测试对比一下才能找好平衡点。

0x05 分页

可参考

5.1 深度分页定义

查询数据、取回过程虽然支持使用 from 和 size 参数进行分页,但是要在有限范围内 (within limited)。from+size查询时,每个shard必须构造一个长度为 from+size 的优先队列,全部传回协调节点,随后协调节点需要对shard数量 * (from + size) 个document进行全局排序来找到正确的 size 个document,最后返回客户端。

根据document的数量,shard量以及硬件状况,对10,000到50,000条结果(1,000到 5,000页)深分页是可行的。但是对于很大的 from 值,排序过程将会变得非常繁重(会使用巨大量的CPU,内存和带宽)。因此,强烈不建议使用深分页。

5.2 解决方法

  • scroll
    可以将scan(扫描) 搜索是和 scroll(滚屏) API一起使用,可从Elasticsearch里高效地取回海量结果,而不需要付出深分页的代价。

    为了解决深分页的问题,elasticsearch提出了一个scroll滚动的方式,原理是每次查询后返回一个scroll_id,并根据这个scroll_id进行下一页的查询,可以理解为关系型数据库中的游标。

    具体来说,一个scroll滚屏搜索允许我们做一个初始阶段搜索并且持续批量从Elasticsearch里拉取结果直到没有结果为止。 滚屏搜索会及时制作Index快照,这个快照不会包含任何在初始阶段搜索请求后对index做的修改

    但是,这种scroll方式的缺点是不能够进行反复查询,也就是说,只能进行下一页,不能返回上一页。

  • scan
    深度分页代价最高的部分是对结果的全局排序,但如果禁用排序,就能以很低的代价获得全部返回结果。为达成这个目的,可以采用 scan(扫描) 搜索让Elasticsearch不排序。只要shard里还有结果可以返回,就返回一批结果。

  • scan scroll
    为了使用scan-and-scroll(扫描和滚屏),需要执行一个搜索请求,将search_type 设置 成 scan ,并且传递一个 scroll 参数来告诉Elasticsearch滚屏应该持续多长时间。

GET /old_index/_search?search_type=scan&scroll=1m
{
    "query": { "match_all": {}},
    "size":  1000
}

上述请求中,将保持滚屏开启1分钟。 该请求的response中不包含任何命中的结果,而是包含了一个Base-64编码的 _scroll_id(滚屏 id) 字符串。随后就可以使用该_scroll_id 传递给 _search/scroll 末端来获取第一批1000条结果。每次循环获取数据的时候都需要传入上一次scroll response返回的_scroll_id。滚屏的终止时间会在我们每次执行滚屏请求时刷新。

  • search_after
    是官方推荐的性能最好的向后翻页,向前翻页不行

0xFE 总结

本文主要讲了一些搜索时调优选项,希望对大家有帮助。

0xFF 参考文档

Elasticsearch: The Definitive Guide

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值