ES查询score问题解析
现象
测试环境,同样的查询条件,返回的结果,命中数量相同,但是排序有变化。
现象可看用例。
{
"from": 0,
"size": 10,
"query": {
"function_score": {
"query": {
"bool": {
"must": [
{
"exists": {
"field": "dbName",
"boost": 1
}
},
{
"bool": {
"should": [
{
"match_phrase": {
"group": {
"query": "XX租户",
"slop": 0,
"zero_terms_query": "NONE",
"boost": 1
}
}
},
{
"term": {
"group": {
"value": "XX租户",
"boost": 1
}
}
}
],
"adjust_pure_negative": true,
"boost": 1
}
},
{
"bool": {
"should": [
{
"terms": {
"groupId": [
1,
2,
7,
9,
14,
85,
87,
88,
93,
181,
193,
194,
195,
198,
199,
201,
202,
204,
205,
207,
208,
210,
211,
212,
213,
218,
10007,
10018,
10070
],
"boost": 1
}
},
{
"bool": {
"must_not": [
{
"exists": {
"field": "group",
"boost": 1
}
}
],
"adjust_pure_negative": true,
"boost": 1
}
},
{
"term": {
"group": {
"value": "",
"boost": 1
}
}
}
],
"adjust_pure_negative": true,
"boost": 1
}
}
],
"must_not": [
{
"term": {
"level": {
"value": "STG",
"boost": 1
}
}
},
{
"term": {
"level": {
"value": "STG",
"boost": 1
}
}
}
],
"adjust_pure_negative": true,
"boost": 1
}
},
"functions": [
{
"filter": {
"term": {
"level": {
"value": "DWD",
"boost": 1
}
}
},
"weight": 100
},
{
"filter": {
"term": {
"level": {
"value": "DWS",
"boost": 1
}
}
},
"weight": 100
}
],
"score_mode": "multiply",
"max_boost": 3.4028235e+38,
"boost": 1
}
},
"sort": [
{
"_score": {
"order": "desc"
}
},
{
"dbName": {
"order": "asc"
}
}
],
"track_total_hits": 2147483647
}
返回结果略,就是同样的文档打分_score不同,同时前后两次查询文档顺序可能不一样。
现象分析:
mysql肯定不会出现这样的问题,而ES相比之下是一个分布式搜索引擎。
我们是不是可以猜测
ES是以shard作为最小计算单元的的,有没有可能是shard之间的差异导致的
原因解析:
查询过程
在elasticsearch搜索时,默认使用QUERY_THEN_FETCH
根据官方文档,QUERY_THEN_FETCH模式搜索步骤如下:
1.发送查询到每个shard
2.找到所有匹配的文档,当然,使用本地的TF/IDF信息进行打分
3.对结果构建一个优先队列(排序,标页等)
4.返回关于结果的足够的元数据到请求节点。注意,不包含文档内容
5.来自所有shard的数据合并起来,并在请求节点上按进行排序,获得要求的分页和数量的文档
最终,实际文档从他们各自所在的独立的shard上检索出来(此时包含文档内容)
本地的TF/IDF信息
每一个shard都是一个Lucene实例,Lucene使用TF/IDF计算相关度算法。而每个Lucene实例只保存了自身的TF和IDF统计信息,所以一个shard只知道term(词条)在其自身中出现的次数,而非整个cluster
TF: Term Frequency的缩写,表示该term在当前document出现的频率
IDF: Inverse Document Frequency缩写,表示该term在所有文档中出现的频率
从TF/IDF算法可以看出,该term在当前文档出现次数越高,那么分值越大;如果该term在所有文档出现的频率越小,那么分值越大。这样term分数,不仅和此篇命中的文档有关,还和该shard的文档数量、文档内容量有关
ES主副shard
es会随机(负载均衡)访问主副分片。
官方文档中,提到分片中存在标记为已删除的文档,这些文档只有在下一次旧文档所属的段合并时才会从磁盘中删除。但是出于实际原因,这些已删除的文档会被考虑用于索引统计。因此,假设主分片刚刚完成了一个大型合并,删除了大量已删除的文档,那么它可能具有与副本(仍有大量已删除文档)完全不同的索引统计信息,因此分数也不同。
https://www.elastic.co/guide/en/elasticsearch/reference/current/consistent-scoring.html
结论1:
不同分片之间计算score的算法是以每个shard本地存储的TF/IDF信息计算出来的,而本地存储的TF/IDF是以该shard中的所有数据算出来的。那么会出现这样的现象,一个内容相同的doc,因为id不同被放到了不同的shard中,那么他们算出来的分数可能不一样,但不是造成两次查询排序结果不一致的原因。
Lucene根据哈希算法分配文档到不同shard,当文档数据量比较大时,哈希结果会使不同shard文档数量趋于一致,默认的方式也能取得相当理想的结果。
结论2:
主副分片中存在数据不一致(软删除数据),导致同一条查询语句,分配到不同的shard中计算出来的score不一样,最终按照score排序后,发现前后两次查询的顺序不一致。