版权声明 本站原创文章 由 萌叔 发表
转载请注明 萌叔 | http://vearne.cc

1. 引子

公司内部有简单的搜索引擎,使用ES搭建。
前两天测试人员问我,为什么同一个查询条件,同一条数据,多次查询。score会发生变化。经过验证,确实存在这种问题,那么这种情况到底怎么产生的呢?

2. 例子

来造个例子

2.1 创建index

curl -XPUT -H "Content-Type: application/json" dev1:9200/test -d '
{
    "settings": {
        "index.number_of_replicas": "2",
        "index.number_of_shards": "1"
    },
    "mappings": {
        "_default_": {
            "dynamic_templates": [],
            "properties": {
                "brand": {
                    "type": "keyword"
                }
            }
        }
    }
}

2.2 写入数据

第1次执行
insert1.py

import requests
for i in range(500):
    url = "http://dev1:9200/test/car/%d" % (i)
    res = requests.put(url, json={"brand":"buick", "age":i})
    print(i, res.status_code)

第2次执行
insert2.py

import requests
for i in range(200):
    url = "http://dev1:9200/test/car/%d" % (i)
    res = requests.put(url, json={"brand":"buick", "age":i})
    print(i, res.status_code)

3.验证

多次执行(可阅读参考资料2)

curl -XPOST -H "Content-Type: application/json" dev1:9200/test/car/1/_explain -d '
{
        "query": {
                "term": {
                        "brand": "buick"
                }
        }
}'

可以观察到分值确实有发生微小的变动

{
    "_index": "test",
    "_type": "car",
    "_id": "1",
    "matched": true,
    "explanation": {
        "value": 0.000893256,
        "description": "weight(brand:buick in 429) [PerFieldSimilarity], result of:",
        "details": [
            {
                "value": 0.000893256,
                "description": "score(doc=429,freq=1.0 = termFreq=1.0\n), product of:",
                "details": [
                    {
                        "value": 0.000893256,
                        "description": "idf, computed as log(1 + (docCount - docFreq + 0.5) / (docFreq + 0.5)) from:",
                        "details": [
                            {
                                "value": 559, # 变化的部分
                                "description": "docFreq",
                                "details": []
                            },
                            {
                                "value": 559, # 变化的部分
                                "description": "docCount",
                                "details": []
                            }
                        ]
                    },
                 ... ...
}

docCount/docFreq 多次执行值都不同。
第1次是 559
第2次是 558
第3次是 560

其实ES中默认的相似度计算打分算法是BM25,BM25类似TD/IDF
先来说docCount/docFreq的含义
docCount是指 shard中的文档个数
docFreq 是指 包含有查询关键词的文档个数。
在此处是指品牌是”buick”的文档个数,由于数据完全是笔者臆造的,每个doc的品牌都是”buick”,因此docCountdocFreq恰好相等。但是笔者通过insert1.py和“insert2.py`写入的数据一共就只有500条,这个559是什么鬼?

笔者安装了cerebro,通过它显示shard stats
image_1ddplf7bomspnj9o2h18m7tg09.png-95.1kB
在不同的shard上

{
  "routing": {
    "state": "STARTED",
    "primary": false,
    "node": "uf_-1wJHSEqgQmgPxanZJA",
    "relocating_node": null
  },
  "docs": {
    "count": 500,
    "deleted": 58
  }
  ...
}
{
  "routing": {
    "state": "STARTED",
    "primary": true,
    "node": "3bo9Z0krShyAK-t_F5Fo8A",
    "relocating_node": null
  },
  "docs": {
    "count": 500,
    "deleted": 59
  }
}
{
  "routing": {
    "state": "STARTED",
    "primary": false,
    "node": "XnajLSWPS2O2n5iO57pdjw",
    "relocating_node": null
  },
  "docs": {
    "count": 500,
    "deleted": 60
  }
}

3. 解释

结合上面的线索,答案已经明了了。
1. 在ES中,相同ID数据的写入是先删除,再插入。(insert1.pyinsert2.py部分数据ID重合)
2. ES中数据的删除通过标记实现的伪删除,只有在segment merge时,才会真正的执行删除,移除被删除的数据。(见我的文章LUCENE索引结构漫谈)
shard中 docCount实际等于 count + deleted

那么为什么shard 0的不同副本, deleted不相同?
笔者猜测,这是由于多个副本中,数据分布在了不同的segment中,而segment的合并是有一定的随机性的。所以deleted并不相同。实时上如果执行force merge

curl -XPOST http://dev1:9200/test/_forcemerge?max_num_segments=1

可以将deleted变为0

提示

对于设置多个shard的index。即使查询条件相同,但由于不同的shard之间docCount不同,即使2条数据完全一样(_id不同),也有可能导致分值不同。

参考资料

  1. ES-similarity
  2. ES-explain
  3. TF-IDF及其算法
  4. preference

如果我的文章对你有帮助,你可以给我打赏以促使我拿出更多的时间和精力来分享我的经验和思考总结。

微信支付码

发表评论

电子邮件地址不会被公开。

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据