elasticsearch之sorting and relevance

来源:互联网 发布:冯伟文是谁 知乎 编辑:程序博客网 时间:2024/06/10 07:43

默认情况下,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来提升用户体验。




0 0
原创粉丝点击