几种常见的查询性能问题及优化方式

发布于 2021-10-08 22:07

Lucene 以及 ES 并非擅长所有类型的查询,其中部分场景存在较严重的性能问题,下面分享几个常见的 case,以及对应的优化方式。

低基数 long 类型的点查

数值类型的点查(term,terms)如果命中数量特别多,在访问叶子节点或者构建 FixedBitSet 的时候存在性能问题,通常的表现是 CPU 占用率非常高。访问叶子节点的时候:

构建 FixedBitSet 用于集合运算,极端情况可能会把内存带宽跑满:

此类问题最常规的优化方式,是修改数据类型为 keyword,无论在计算 cost 还是集合运算的时候都比 BKD 树优势大的多。但是要注意如果会聚合在这个字段上,构建全局序数会带来比较大的延迟和内存占用。

如果查询语句中有其他过滤条件,可以得到一个较小的结果集,可以将 term 查询改写为等价的 range 查询,类似的,将 terms 查询改写为等价的 bool + range 查询,这样可以使用 indexOrDocValueQuery 中的优化,避免构建 bitset。

两种优化均可大幅降低 CPU 占用率。

构建大量全局序数

keyword 类型在聚合过程中可能需要构建全局序数(除了 force merge 为 1 的情况),全局序数需要常驻 JVM 空间,对于总是强调 31G 内存上限的 ES 来说,在某些场景可能会有严重性能问题。过多占用 JVM 空间导致进程 GC 严重,导致读写大幅延迟。通过 fielddata 或者 parent breaker 指标可以大致确定全局序数的占用情况。

应急的解决方式是将聚合方式修改为 "execution_hint": "map",一般基数在百万以下的改为 map 方式会更优(字段值特别长的例外)。

pre-filter 导致长尾

如果你的节点负载不高,查询队列也没有很多 reject,GC 和 JVM 指标正常,而经常有查询毛刺,你从 access log 中找到他们,重跑一次 took 都在秒内返回,那么很可能与查询过程部分节点的长尾有关,这类查询的 total_shards 经常有几百, total_shards 超过 128 的查询会经历 pre-filter 阶段,这个阶段有那么一丢丢好处,但是可能带来严重的长尾:

关闭 pre-filter 可以明显改善这种长尾问题,虽然 query + fetch 也可能有长尾,但几率会降低很多。目前长尾的原因暂时还不清楚,猜测可能在 transport 层。

多维度 groupby

对于常见的 group by A,B,C 这种查询,嵌套聚合的性能很差,嵌套聚合被设计为在每个桶内进行指标计算,对于平铺的 group by来说有存在很多冗余计算,另外在 meta 字段上的序列化反序列化代价也非常大,这类 group by 替换为 composite 可以将查询速度提升 2 倍左右,目前字节重写了聚合层,计算性能提升一个数量级,详情可见 datafun 或者 es meetup 的分享。

ShardSearchFailure 导致协调节点 OOM

7.x 中有一个很好的特性是当客户端查询断开连接的时候,会 cancel 运行中的查询,减少无意义的资源消耗,但是这个特性带来一个意外的问题,数据节点返回给协调节点的 ShardSearchFailure 可能会把协调节点内存撑爆。

例如,当客户端断开连接,协调节点会发送 cancelTask 的 RPC 给数据节点,数据节点在执行 query 阶段时发现任务已经被取消,会返回 ShardSearchFailure,其中携带堆栈信息,因此返回体较大,同时协调节点也比较无脑,要等待所有的分片返回 response 才会走下面的流程,给客户端发送结果,这也导致 ShardSearchFailure 信息会在协调节点内存中积压一段时间,这段时间取决于长尾的程度,一种 case 是,某个节点 CPU 跑满,大概率导致查询资源不足,从而触发 search cancel,而协调节点要等待全部结果,由于慢节点的存在,JVM 中积压大量 ShardSearchFailure 对象,最终 OOM。

本文来自网络或网友投稿,如有侵犯您的权益,请发邮件至:aisoutu@outlook.com 我们将第一时间删除。

相关素材