作者:刘帅 企查查搜索架构师
一、ES在企查查查的使用情况
目前,ES在企查查内部已经有10+集群,300+节点,2500+索引,索引存储约100TB。
ES在企查查的主要应用场景有三类:
- 全文检索:主要用于企业、风险、信用、司法等维度的信息检索。与其他搜索引擎不同的是,企查查内部要求100%的结果召回率。在此基础之上,还应用了实时的聚合分析以及动态高亮。
- 关联查询:主要用于建筑查查、注册人员注册到期等业务场景,解决了查子返父与查父返子的两个业务问题,使用ES的嵌套查询与父子查询实现。
- 动态聚合分析:主要用于风险监控、财产线索、信用大数据、司法大数据等业务场景,用于多级分类与汇总,快速找到具体风险点。
上图为搜索引擎的垂搜列表页,全文检索和实时聚合分析是两个常用功能。我们采用了后端高亮,并没有采用前端高亮,可以明显看到关键词与命中字段之间的映射关系,用户能够更容易地知道通过哪字段命中或召回。
此处使用了阿里云的达摩院分词器,对于人名与公司名的识别有奇效,非常推荐使用。
关联查询场景下,通过两个子维度的查询能得到主维度的信息。结合企业资质信息的查询条件与四库业绩查询的条件,最终可以得到工商信息,直接找出满足两个条件的公司,而不是返回资质信息的维度或四库业绩的维度,减少一次查询。用户可以更容易地得到相应的公司以及明确它由哪几个子维度命中。
动态聚合分析的作用是快速了解、定位公司风险。
以乐视为例,该公司下存在司法风险等诸多风险,司法风险下又分为失信、限高、被执行等。并且在此基础之上又增强了版本,提供了关联的风险查询能力,可以再一次聚合其相关联的公司以及关联公司内部的风险。
但是在技术层面上,对公司或人员直接进行聚合会涉及到高基度聚合分析,存在较大挑战。
二、实际生产中的痛点与挑战
可用性方面,由于业务的复杂程度与关联查询消耗较大的请求导致CPU与内存使用率飙升,导致整个集群的服务异常,无法保障搜索服务99.99%的可用性。
性能方面,存在查询超时、查询毛刺。无法满足100%召回率、聚合分析、关联查询以及大文本的高亮。
成本方面:主要来自CPU、内存与存储成本,挑战在于如何权衡低收益、高成本的查询。
针对上述痛点与挑战,我们将通过两个实际的案例进行定位与分析。
三、问题的定位与分析
在企查查内部,每个接口的上限都会伴随着压力测试与性能回归。
某次压测后,我们在第一时间通过阿里云的日志服务收到了来自接口与ES前置代理的报警。通过快速指标分析发现,搜索的请求相比于前一天暴增一倍多,但针对详情页面的检索量没有上升,反而下降。
基于这两个指标的判定,可以得出第一个结论:流量的暴增不是来自外部,而是来自内部。通过与阿里云同事的沟通以及结合自身的指标判定,我们得出了结论:WAF会有三次重试,当WAF与源站建联超过5秒或状态码为502/504时,都会有重试。因此我们猜测,多出的流量可能来自WAF对于异常请求的重试。
至此,我们得到了几个有比较有用的指标:首先,WAF层有3次重试,另外有两条线,针对WAF的重试乘以2,再加上本身的流量,最极限的情况下流量会翻7倍。从日志服务里发现有大量告警,意味着是节点导致out of memory。
自底向上从ES内部定位分析,发现ES的query_cache在压测时有了暴增。对此,我们做了大胆的假设,可能是由于query_cache导致的数据节点内存溢出。
基于该假设,我们使用了MAT对内存进行了分析,证实了我们的想法。从mat的分析图表中可以看到,77.48%来自索引的query_cache,因此有理由相信OOM由query_cache引发。
我们的结论也从官方的issue里得到了正式。从官方issue看,是由于Global OrdinalQuery污染了query_cache导致。针对该问题,我们做了简单的尝试,将整个索引的query_cache关闭,再进行了一轮小规模压测,节点没有继续OOM,问题得到缓解。
但query_cache不能一直关闭,因此我们尝试了另外一种方法,针对带有聚合且size=0的业务请求,将size设置为1,不走query_cache,从一定成都上也规避了风险。
经过此次压力测试,我们总结出了两个问题:
其一,ES的内部issue加速了数据节点的OOM;其二,请求ES异常时,WAF的重试动作会加剧,或会放大异常情况。
关联查询并不是很昂贵的搜索,我们针对有对于需要关联查询的场景做了profile。
发现大量关于关联查询的慢查询主要耗时多分布在scorequery阶段,我们有理由相信,只要将该阶段进行优化,即可实现性能上质的飞升。
针对该问题,官方也做了简单优化。默认情况下,Global Ordinal在Refresh阶段进行刷新,可以通过在创建mapping时,设置为在索引期间将缓存构建好,可以极大减少在查询期间由于加载Global Ordinal而导致的慢查询。
除了该优化方案,我们还做了定位分析。如果在子维度里没有做任何限制,则往往查询效率极差。如果可以使用过滤条件减少子纬度的数据集,效果可能会有明显十倍的提升。
基于此,我们希望尽量将父级的过滤条件下放到子维度中,尽量减少子维度检索中的数据集。
通过定位分析,我们也发现了两个问题。
主要问题为:用户数据查询时,加载Global Ordinal的缓存数据会导致整个查询请求变慢;次要问题为:在父子查询中,子维度命中文档越多,性能越差。
四、优化方案的确定与实施
针对上述两个问题,我们也做了优化方案。
针对主要问题ES内部issue加速了数据节点的OOM,有3个可选项:第一,完全关闭了索引的query_cache;第二,在低版本的ES还未修复之前,将聚合分析查询的size设置为1,尽量使查询不进query_cache,减少内存的使用,也可以避免节点OOM的发生;第三,官方已经确认了异常,在更高的版本里已经做了修复,可以通过升级ES版本解决OOM的问题。
另外,请求异常时,WAF的重试动作会放大后端的业务请求以及放大异常的情况。
针对该问题,首先可以与阿里云的同事进行确认,关闭WAF的重试机制,在个proxy层增加限流的策略;在ES集群内部,可以使用阿里云的限流插件,针对每个索引做一定程度的限流。
针对用户在数据查询时加载Global Ordinal会导致性能急剧下降,可以在构建构引时加载Global Ordinal。
针对在父子查询中,子维度中文档命中越多性能越差的问题,较好的解决方案是:将父级的字段冗余到子维度中,在检索时将父级的过滤条件下推至子维度,性能可提升3-10倍。