elasticsearch之sorting and relevance

本文深入探讨了Elasticsearch排序机制的核心内容,包括相关性计算、排序参数使用、Fielddata内存管理原理及其应用场景。通过实例分析,详细讲解了如何在搜索查询中应用排序策略,以及Fielddata如何优化排序性能并减少内存占用,旨在帮助开发者更高效地利用Elasticsearch进行复杂查询和数据排序。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

默认情况下,es中的返回结果是根据relevance排序的,相关性最强的结果在最前边。接下来我们会介绍相关性是什么意思,是如何计算的?但是,我们先把焦点放在sort这个参数上,看看如何使用

1:sorting

为了能按照相关性排序,我们需要用一个数值来代表相关性。在es中,_score字段就是表示相关性的数字,用浮点类型表示,默认情况系按照_score的降序排列。

有些情况下,我们并没有一个有意义的相关性数值。比如:下边的query只是要返回user_id为1的doc:

GET /_search
{
    "query" : {
        "filtered" : {
            "filter" : {
                "term" : {
                    "user_id" : 1
                }
            }
        }
    }
}

filter并不需要计算相关性,因此也不需要计算_score,另外match_all这种类型的查询把_score值都设置成为1,也就是说把所有doc的相关性都认为是一样的。

sorting by field value:

下边这个例子中,根据时效性排序可能是有意义的,最新的记录放在最前边。我们可以用sort这个参数来实现:

GET /_search
{
    "query" : {
        "filtered" : {
            "filter" : { "term" : { "user_id" : 1 }}
        }
    },
    "sort": { "date": { "order": "desc" }}
}
在返回结果中,你会发现有些不同:

"hits" : {
    "total" :           6,
    "max_score" :       null, (1)
    "hits" : [ {
        "_index" :      "us",
        "_type" :       "tweet",
        "_id" :         "14",
        "_score" :      null,  (2)
        "_source" :     {
             "date":    "2014-09-24",
             ...
        },
        "sort" :        [ 1411516800000 ] (3) 
    },
    ...
}
(1)(2)_score并没有被计算,因为不用它来进行排序

(3)返回结果中有了sort值,是date的字段的milliseconds

可以看到:在没一行返回结果中,没一条记录都有了一个元素叫做sort,包含了用来排序的字段的值;_score和max_score都是null。计算score是一个昂贵的操作,而我们这里并不像用score来进行排序,因此我们并不需要对score进行计算,如果无论如何都想要计算score值,我们可以设置track_socres为true。

multilevel sorting:

多个排序标准,比如先按照date排序再按照score排序:

GET /_search
{
    "query" : {
        "filtered" : {
            "query":   { "match": { "tweet": "manage text search" }},
            "filter" : { "term" : { "user_id" : 2 }}
        }
    },
    "sort": [
        { "date":   { "order": "desc" }},
        { "_score": { "order": "desc" }}
    ]
}
注意顺序是很重要的,结果首先按照第一个标准排序,依次类推。

多重标准的排序并不一定要仿照上边的例子一样绑定_score,完全可以任意指定字段,也可以用自定义的字段用脚本计算。

query_string也支持sort:

GET /_search?sort=date:desc&sort=_score&q=search


sorting on multivalue fields:

如果对多值字段排序,请记住,这些值并没有内在的顺序。一个多值字段只是一系列数值的集合,我们应该选择哪一个数值进行排序呢?

对于数值类型和日期类型,我们可以选择max/min/avg/sum等作为排序标准。例如:可以对date字段排序,运用每一个doc中date多值中的最早的时间最为排序标准。

"sort": {
    "dates": {
        "order": "asc",
        "mode":  "min"
    }
}

2:string sorting and multifields

analyzed的string field通常都是多值的,但是在这些field上进行的排序操作多半不是我们想要的结果。比如我们一个field经过analyzed后成为3个term:fine old art,我们期望先按照第一个term排序,再按照第二个,依次类推。但是es在排序的过程中并没有对这些信息的处理。你可以用min max等制定排序模式,但是也只是选定了 old或者 art作为了排序依据。

因此,如果想要对一个string field进行排序,那么这个field必须只含有一个term,但是大多数string field是包含多个term的。那么问题来了,是不是想起了multi field的呢?是的,就是这么干的,主属性用analyzed,副属性用not_analyzed作为排序的字段。

注意:如果真要对一个analyzed string field进行排序,会消耗很多内存,请参见后续的FieldData环节。

3:what is relevance?

这个就是tf/idf,之前博客有介绍过。主要考量三个因素:term frequence,inverse document frequence,field-length norm。

如果想要对一个复杂的query进行debug,弄清楚_score计算的每一个细节是比较困难的。es也提供了explain参数来查看score计算的某些参数值:

"_explanation": { 
   "description": "weight(tweet:honeymoon in 0)
                  [PerFieldSimilarity], result of:",
   "value":       0.076713204,
   "details": [
      {
         "description": "fieldWeight in 0, product of:",
         "value":       0.076713204,
         "details": [
            {  
               "description": "tf(freq=1.0), with freq of:",
               "value":       1,
               "details": [
                  {
                     "description": "termFreq=1.0",
                     "value":       1
                  }
               ]
            },
            { 
               "description": "idf(docFreq=1, maxDocs=1)",
               "value":       0.30685282
            },
            { 
               "description": "fieldNorm(doc=0)",
               "value":        0.25,
            }
         ]
      }
   ]
}
注意,explain操作是非常昂贵的操作,在生产环境中尽量不要使用,只是作为debug比较好。

explain的输出是对每一条结果的一个解释,同时我们可以用explain api来查看某一条为什么被match到了?为什么没有被match到。

GET /us/tweet/12/_explain
{
   "query" : {
      "filtered" : {
         "filter" : { "term" :  { "user_id" : 2           }},
         "query" :  { "match" : { "tweet" :   "honeymoon" }}
      }
   }
}

除了前边的信息,我们会发现有这么一条输出:

"failure to match filter: cache(user_id:[2 TO 2])"

也就是说,是used_id字段使得这行记录没有被match到!

4:fielddata

当我们要对一个field进行排序的时候,es需要访问到match到的doc中这个field的值。对倒排索引来说,search的性能是非常好的,但是这种结构不适合sort操作。

当search的时候,我们是把一个term对应到一系列的docs,而当sort的时候,我们是要把一个doc对应到terms,也就是说我们是“uninvert”这个inverted index。

为了能够使得sort操作更为高效,es把对应field的所有值都加载到内存中,也就是我们说的fielddata。

注意:es不仅仅把对应query所match到的document的值加载到内存,而是把索引的所有document加载到内存,并不去区分type。

es这样做的原因是:从磁盘中去uninvert倒排索引太慢了。即使你当前的request只是需要一部分docs,你可以在下一个request会访问其余的docs,所以直接一次把所有doc都加载到内存中。

fielddata在许多场景下都有应用:

对指定字段排序

对指定字段aggregation

某些特定的filter(比如geolocation filters)

对一些field执行script操作

等等。

显然,这回消耗掉很大的内存,尤其是对基数很高的string类型的field,这些field包含许多不同的term,比如email的内容。幸运的是,内存不足可以用水平扩展来解决,在集群中增加更多的节点。

截至到目前,我们应该了解到fielddata是什么?意识到fielddata会消耗到大量的内存。之后,我们会接触到如何决定fielddata消耗内存的大小,如何限制可用内存的数量以及如何提前加载fielddata来提升用户体验。




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值