(接上篇)搜索引擎从接收到查询请求到返回响应结果,中间需要经过多个数据处理步骤,如果能够从流程上优化,节约不必要的消耗,也同样能够提升性能表现,而且效果经常还不错,这次就来聊聊查询过程优化。
2. 查询过程优化
过程优化往往能够在性能优化初期带来明显的性能提升,快速的业务开发必然会对代码质量造成一定影响,也会使一些代码逻辑变得冗余甚至毫无意义。
- 逻辑优化
要实现某个业务效果,可以有很多种方法,彼此间性能表现各不相同,比如查询100页订单数据,这时分页查询就是一种逻辑优化,性能表现或者说用户体验必然要高于一次性加载全部数据返回的方式。
搜索引擎查询也是类似的,比如一次复杂的排序查询,用户对于5页之后的数据体验明显要低于首页,此时可以通过简化粗排加速查询过程,仅对top n数据进行rerank来提升性能表现,减少不必要的计算;对于长列表展示,可以首先返回用户最常访问的前n页数据,在用户浏览快要结束时再预加载后续页数据。
逻辑优化可以通过减少不必要的数据操作来提高性能,而查询调优则是通过合理利用引擎能力来实现。
- 查询调优
查询组织的好坏会影响对查询的性能表现,有时甚至是比较明显的影响。以ElasticSearch为例,能否合理利用其提供的各项优化技巧,或者避免一些高消耗的查询方式,都会影响某个查询的响应时间,大部分的高消耗操作在我们的Proxy中都进行了限制,这里只说说几个经常出现的点:
- filter
ES在执行查询过程中,会先分解query为最细粒度的子条件,然后通过各个子条件之间的逻辑关系,交并得到最终结果。
在这个过程中,filter是ES提升性能的一种方法,是将某些细粒度子条件结果缓存起来(filter),使得下一次查询减去了从索引文件中匹配的开销,从而提高响应速度。
开启filter缓存只需要在查询中带有filter子句即可,另外需要提到的是,filter缓存是可以跟随文档变更而变更的,即命中这个子条件的文档个数变更都会同时更新到filter缓存,简化了使用。
ES现在还无法做到资源的二级调度,同一集群内的所有索引共享同一个资源池,如果某个子条件的复用率很低,却被放到了filter子句中,那么这个条件的filter缓存就会污染缓存池,非但没有被复用到,而且还会导致正常的filter缓存被LRU策略踢出,影响其它查询的响应。
查询优化rule 1:
复用率高的条件放到filter子句,低复用率的(典型的比如标记单条记录的id字段,通常复用率很低)放在query子句。
- sort
为了给排序提速,ES会将排序用到的字段内容加载到fielddata缓存中,如果某个排序条件只是偶尔出现一次,那么与低复用率的filter条件类似,非但没有体现fielddata的优势反而挤出了正常的fielddata,影响其它查询请求,类似的查询最好能够手动指定此次查询不进入缓存。
查询优化rule 2:
能不要排序的就不要排序,保证排序条件有较高的复用率。
- range
范围查询因为其实现方式的原因(Lucene的range条件是解析为很多term条件实现的)是比较费资源的,如果过滤的是时间范围并且范围比较大(比如到年级),如果时间跨度还经常变,那么这个range条件的性能表现也是比较低的(冷查询多+range费资源),这时候可以考虑空间换时间的做法,比如给索引增加年份字段,用一个term或者terms条件就可以过滤到所需结果了。
查询优化rule 3:
对于查询多变的场景,可以适当考虑空间换时间。
- window size
一次查询需要从n个shard中取from size条数据回来,然后聚合n*(from size)条数据,以优先级队列作为容器,依次计算每条记录的排序权重(匹配分或者排序字段值)得到from size条数,再取末尾的size条数据返回。可以看到中间结果会有很多临时内存被开辟,这就对gc造成了不小的压力,所以控制一次查询的window size(=from size)对于保证引擎性能稳定非常重要,ES1.x版本是需要查询发起方自行控制的,到了5.x版本就自带有window size控制(默认不超过10000),也从侧面说明了这一点的重要性。
查询优化rule 4:
限制查询结果可达页数,不要做深翻页。
- 结果缓存
缓存是提高性能表现的绝佳方式,同时如果后端对突发的流量比较敏感,缓存也是帮助后端平滑压力的防火墙。
ElasticSearch低版本只有中间结果缓存(filter cache)和字段值缓存(fielddata),能够在保证数据实时性的情况下解决力所能及的性能问题,但是ElasticSearch对抖动的qps还是比较敏感的(涉及缓存更替,包括利用操作系统缓存的索引文件),结果缓存是必然需要的。
典型的比如match操作,因为match需要计算的匹配度模型的关系(经典的tf/idf就需要根据doc数量作为计算因子,如果加了缓存就不好做了),ES是不会对match做缓存的,这时候如果有突发的一波请求,那么cpu很容易被打满,进而引起search队列溢出导致拒绝请求,造成服务响应毛刺甚至长时间无响应。类似情况下,短时间内大批量的相同查询请求根本没有必要实时计算结果,结果缓存不仅可以解决qps波峰的问题,还能顺便缩短响应时间,提高查询性能。
查询过程优化是需要通过业务编码来实现的,极致的性能优化不是由某个团队单独完成的,而是一个团队协作的过程。
希望所写的这些对大家有所帮助。(全文完)