? ? ? ? 生产环境频繁报警。查询跨度91天的数据,请求耗时已经来到了30+s。报警的阈值为5s。
查询关键词简单,为‘北京’
单次仅检索两个字段
查询时间跨度为91天,覆盖数据为450亿数据
使用profle分析,复现监控报警的语句,确实慢。集群分片太多,这里放一个分片的内容。
{
"id" : "[YWAxM5F9Q0G1PXfTtYZKkzQ][_20230921-000001][3]",
"searches" : [
{
"query" : [
{
"type" : "FunctionScoreQuery",
"description" : "function score (+((title:北京)^2.0 | content:北京) +publish_time:[1687431307000 TO 1695254417999] +es_insert_time:[-9223372036854775808 TO 1703084327999], functions: [{scriptScript{type=stored, lang='null', idOrCode='search-score', options=null, params={}}}])",
"time" : "10s",
"time_in_nanos" : 10079315883,
"breakdown" : {
"set_min_competitive_score_count" : 0,
"match_count" : 150,
"shallow_advance_count" : 0,
"set_min_competitive_score" : 0,
"next_doc" : 2646164,
"match" : 996954485,
"next_doc_count" : 154,
"score_count" : 31,
"compute_max_score_count" : 0,
"compute_max_score" : 0,
"advance" : 1035917137,
"advance_count" : 16,
"score" : 3532211704,
"build_scorer_count" : 40,
"create_weight" : 3965124112,
"shallow_advance" : 0,
"create_weight_count" : 1,
"build_scorer" : 546462281
},
?
在Elasticsearch?Profile?API结果中,主要关注查询的time和breakdown字段,这提供了查询执行的总时间和各个步骤的时间分解。在这个例子中,查询的总时间为10秒。
具体来看,主要的时间花费在FunctionScoreQuery的create_weight步骤上,该步骤耗时为3,965,124,112纳秒(大约3.97秒)。create_weight是在查询执行之前创建用于评分的权重的阶段。
以下是一些步骤的关键信息:
从我的查询条件来看,请求是很简单的,没有复杂的条件,为什么?create_weight?过程耗时会这么长?
create_weight阶段的耗时主要取决于查询中使用的权重计算方式以及索引的结构和数据量。在你提供的Profile?API结果中,create_weight的耗时非常大,说明这个步骤在整个查询过程中占用了大量的时间。
有几个潜在的原因可能导致create_weight步骤的性能下降:
这里主要是lucene去IO底层文件。这里比较明显的是性能问题。
脚本排序的时间会算在create_weight过程中(猜想,待验证)
测试把我的搜索条件,去掉脚本排序。原来是15s,现在是10s,脚本排序的耗时在我请求中,占据了30%多。
其中,耗时最长的分片还是,create_weight?过程耗时最严重。
耗时发生在我的title字段上的这个子查询上。
检索耗时进一步降低。
其中还是有耗时长的个别分片
整个请求6.2s,在这个分片上的请求就花了6s,并且时间还是花在了create_weight上。
降低terminate_after的值可以降低,代价是影响整体的排序效果。
减少段的个数,可以减少耗时。通过段合并。因为可以减少段的遍历。
?
GET?_cat/nodes?v
??并非所有的请求,都需要每个分片都200条数据。特别在大的时间跨度下,分片可能会非常多,动辄几千个,以2000个分片算,最多会匹配2000*200=400000数据。加上脚本排序,这40W数据,都需要参与分数的计算,最终才能角逐出top20的数据。最终的结果是请求耗时长。
??实际上,terminate_after的取值,是可以动态调整的。检索分为乐观和悲观情况,乐观情况下,数据分布是均匀的,在分片上分配是均匀的,且检索条件命中的数据较多。在悲观情况下,检索的数据分布不均匀,且搜索的条件比较特殊,命中的数据很少,或者命中的数据在分片上分布不均匀。
??大多数情况下,数据分布是均匀的,检索的数据量越大,分布可能越均匀。例如检索3个月,总数据大约450亿数据,随便一个搜索条件,搜索的数据大概率是大于10000条的。所以可以设计一个动态调整方案,来调整terminate_after的取值,能够获取更好的性能,提升200%-300%。另外需要一个悲观情况下的担保机制,避免在悲观情况下检索丢失数据。
??terminate_after的值是限定在分片上的,假如一个索引有10个分片,如果设置terminate_after为200,则最后返回的数据总量为?10*200=2000条。考虑到分页为500页,每页20条数据,共计可以翻页10000条数据。如何设置terminate_after的值呢?要考虑到翻页的情况。
??请求的入参,一般包含了翻页和每页的条数。?期望数据总量=?页码*?每页的数量。??es的召回总量为=?分片数*terminate_after数量*偏差。偏差可以算0.1,预期10倍可以弥补数据分布不均匀带来的影响。分片数暂时可以按每天15个来算。?页码*?每页的数量?=?分片数*terminate_after数量*偏差?。可以得出??terminate_after数量?=?页码*?每页的数量?/?(分片数*偏差)。terminate_after数量不足10则向上取正为10。?当查询的天数小于7天,则可以直接取值为200。
??担保机制,需要解决悲观情况下的问题。根据es返回的数据总量。?如果返回的数据总量小于期望的数据总量,则触发担保机制。需要调大terminate_after的值(暂定为500),再去搜索一次。
??段合并可以提升减速效果。