几种常见的查询性能问题(二)
查询层面的性能问题一部分是设计上本意,另一种是工程实现上的问题,设计上很难全面照顾各种场景,尤其是一些极端的场景,工程实现上除了 bug 之外也可能有作者脑海中一些预设的假定,导致个别场景遇到问题,没有办法一步到位,只是设计上取舍,在迭代中前进。
terms query 中一次查询海量值
一种比较极端,但是很典型的场景是在画像,或者广告场景中一次点查几千,几万,甚至更多的值:
1 2 3 4 5 6 7 8 9 |
"terms": { "field1": [ "xxx", "xxx", "..." ] } |
这类查询的 total hits 不一定很大,但是原生的性能并不理想,能达到的 QPS 很低。主要有两方面的原因:
- 可以想象到,几万个条件求交集,按 lucene 合并倒排表的实现,效率高不到哪里去
- 查询请求体比较大,序列化的代价非常可观
对于几万个条件求交集的问题,目前最常见的解法(例如 Doris)是压根不去查field1,先把传入的几万个值存到 hashmap 之类的数据结构,然后根据过滤条件中其他字段的过滤结果,到 hashmap 中去判断是否存在(简称match方式),这要求最好有其他过滤条件已经得到了一个较小的结果集,lucene 中称为存在 lead 条件,这样可以极大提升查询效率,可以基于 DocValuesNumbersQuery 适当改造实现。
请求体较大导致序列化的代价也比较大,在较早的版本中,协调节点重组为分片级别请求时存在重复序列化的问题,查询 100 个会将请求序列化 100 次,组内的同学已经优化该问题并合并到社区。
另外一种解法是在客户端侧就将 terms中的值转换成 bitmap,发给 es 去做 match,思路和上述一致,解决序列化和实现 match方式的查询。有两个不同的实现:
https://luis-sena.medium.com/improve-elasticsearch-filtering-performance-10x-using-this-plugin-8c6485516c1a
View story at Medium.com
match 方式的查询在实际应用中需要确定一个阈值,其收益取决于 terms 元素的个数,如果很少,还是走索引查询更快,Doris 中设置的为 1024。
倒排索引的点查性能
前一篇说过 bkd 索引的点查在 total hit 比较大的情况下容易导致 cpu 跑满,倒排索引的点查性能要比 bkd 好很多,但在一些极限场景下也存在性能问题。
第一种是查询的 total hits 很大,例如达到几十万,或者上亿,也会容易把 CPU 跑满,对于这种查询系统能承受的 QPS 很低,几十台机器可能只能承受几十 QPS。火焰图里全是 advance 在迭代。
这类问题有时候是业务忘记带过滤条件,这种情况可以在请求中加 terminate_after 参数作为兜底策略,另外也可能是正常的业务请求,这种暴力计算 lucene 没有太多办法。从索引上来说 bitmap 索引更适合这种查询,bitmap 之间直接位运算来求交并补,比迭代要快很多。
第二种是 terms 查询条件非常多并且每个查询之间的重复几率不大,这种查询在 HDD 盘上容易把 io跑满。除了调分片尽量利用多盘的能力,forcemerge 也会有不错的效果。
关于计算并行度
查询的性能问题一部分在 query 层面的数据过滤,另一个是聚合计算,一个查询在单一分片上,基于过滤结果用指定的算子进行计算的过程是串行的。如果 total hits 比较大,聚合计算也会比较慢。要增加计算并行度只能增加主分片数量。系统设计就是如此,有些系统会设计为让查询尽快跑完,es 更追求兼顾其他请求。
另外如果节点数大于分片数,增加副分片数量也可以提升 QPS 处理能力,但是一般不需要,除非你明确的知道为什么这么做。
查询压力在物理机之间负载不均
有一种情况是集群的少数几个节点 CPU跑满,导致整体的写入lag 或者查询超时,物理机之间的负载不均,常见的几种 case:
- 日期滚动性索引,当天创建的索引分片在节点之间分配不均衡。当节点数大于分片数,一天创建 N 个索引时,可能部分节点持有的分片更多,不同索引之间的均衡 es 是没有保障的。如果主要流量正好是查询当天索引,持有当天索引分片最多的节点负载就会明显高于其他节点。
- 日期滚动性索引,当天创建的索引分片在物理机之间分配不均衡。与上述场景类似,在单机部署多实例的情况下,可能节点之间的分片只相差 1-2 个,但是可能都集中在某个物理机,又或者异构、物理机启动的节点个数不同的情况下。
- 不同索引之间的资源竞争,A 业务的查询把 CPU 跑满,导致其他索引全都受影响
- 瞬时比较大的写入压力,把 CPU 占满,可能瞬时产出数据较多,lag 后追平等影响,因为读写共享计算资源,相关请求都受影响。
es 分片的均衡策略是尽量让节点之间的分片数量尽量相同,无法考虑负载,但有时候确实需要从负载层面去调整分片的分布,有些问题可以调节分配策略勉强用,但最好还是引擎能支持的更好。
资源隔离是个比较大的话题,除了上述几种情况还有 GC 的影响,IO 竞争之类,最简单的隔离还是为分片划分特定的独占节点,或者单独划分集群。
分析方法
慢查询的原因多种多样,但是绝大部分都是容易定位到的:
- 如果 CPU 高,看火焰图,最好懂 lucene,如果 CPU 没有跑满,慢查询可以从 slowlog 中找到
- 如果 thread queue 堆积,可以看堆栈和 hot_threads(不能完全取代堆栈)
- profile 适合分析纯计算层面各个阶段的代价,他主要展示 lucene 的执行过程
- 分布式层面可以通过 trace 或者日志等来协助定位
(转载请注明作者和出处 easyice.cn ,请勿用于任何商业用途)