Elasticsearch:分布式计分

1000 篇文章 594 订阅

Elasticsearch 提供了一个最重要的功能就是相关性。它可以帮我们按照我们搜索的条件进行相关性计算。每个文档有一个叫做 _score 的分数。Elasticsearch 使用了一些相关性算法,默认是 Okapi Best Matching 25 (BM25) 算法。在默认没有 sort 的情况下,返回的文档时按照分数的大小从大到小进行排列的。这个分数的计算是按照如下的三个条件来进行计算的:

1) Term Frequency (TF):给定术语在某个文档中的使用频率。在一个字段中该术语出现的越多,这个术语越重要。

TF 的计算永远是100%的精确,这是因为它是一个文档级的计算。比如如下的文档,我们计算 Java 的 term frequency:

如表所示,Java 在 ID 为 25 的文档中出现了 3 次,而在其他两个文档中出现了 1 次。 如表所示,搜索词在第一个文档中出现的次数更多(ID 为 25)。 考虑到 25 的文档 ID 是我们最喜欢的,这是合乎逻辑的。 请记住,频率越高,相关性就越大。 在上面的曲线中,我们可以看出来 term frequency 大到一定的时候,它对 score 的贡献就不那么明显了,比如到 term frequencu 为 10。当超过这个值后,对 score 的影响也不是很大。

2)Inverse Document Frequency (IDF): 给定术语在所有文档中的唯一性。一个字段在越多的文档中出现,那么这个术语就越不重要,比如 “the”,"to" 等这些词经常出现在一些文档,那么这些词的重要性就不强。搜索词在整个文档集(即整个索引)中出现的次数是文档频率。 如果一个词的文档频率较高,我们可以推断该搜索词确实在整个索引中是常见的。 这意味着如果该词在索引中的所有文档中出现多次,则它是一个常用词,因此它并不那么相关。文档频率的倒数(称为逆文档频率或 IDF)为整个索引中的不常见单词提供了更高的重要性。 因此,文档频率越高,相关性越低。 下面的图显示了词频与相关性的关系。

IDF 的计算不一定是100%的精确。在默认的 query-then-fetch 计算中,它是在本地针对每个 shard 来计算的。在绝大多数的情况下,这个绝不是一个问题:

  1. 使用本地 IDF 很少出现问题,尤其是对于大型数据集
  2. 如果你的文档在各个分片之间分布良好,则本地分片之间的 IDF 将基本相同
词频相关性
更高 term frequency更高相关性
更高 document frequency更低相关性

3)Field length:较短的字段比较长的字段更相关。比如:

在上面搜索的结果中,每个文档的 title 都含有 Java,但是第一个文档只有两个单词,是最短的,所以它的分数是最高的。 包含两个词的第一个标题(Effective Java)中的搜索词比包含三个词的第二个标题(Head First Java)更相关。

相关性算法使用的是 TF-IDF。那么在计算相关性时,是否需要知道整个索引的 TF-IDF 还是每个分片(shard)的 TF-IDT?

默认搜索类型:“query-then-fetch”

默认情况下,Elasticsearch 将使用一种称为 “先查询后取” 的搜索类型。其工作方式如下:

  1. 将查询发送到每个分片
  2. 查找所有匹配的文档并使用本地 Term/Frequency 计算分数
  3. 建立结果优先级队列(排序,from/to 分页等)
  4. 将有关结果的元数据返回到请求节点。注意,实际文件还没有发送,只是分数
  5. 来自所有分片的分数在请求节点上合并并排序,根据查询条件选择文档
  6. 最后,从文档所在的各个分片中检索实际文档。
  7. 结果返回给客户

该系统通常运行良好。在大多数情况下,你的索引具有足够的文档,可以使 term/document 文档频率统计数据变得平滑。因此,尽管每个碎片可能不完全了解整个群集的频率,但结果“足够好”,因为各地的频率都非常相似。

默认搜索类型有时会失败。

DFS Query Then Fetch

如果遇到这种评分差异有问题的情况,则ES提供一种称为 “DFS Query Then Fetch” 的搜索类型。除了执行预查询以计算全局文档频率外,该过程几乎与 “Query-then-Fetch” 相同。

为了使得 IDF 100%精确,在分片可以计算每个匹配的 _score 之前,必须全局计算其值。那么问题来了:为什么我们不为每一个搜索都计算全局的 IDF 呢?答案是这样的计算会增加很多的开销。

  1. 预查询每个分片,询问术语和文档频率
  2. 将查询发送到每个分片
  3. 查找所有匹配的文档并使用从预查询中计算出的全局 term/document 频率来计算分数。
  4. 建立结果优先级队列(排序,从/到分页等)
  5. 将有关结果的元数据返回到请求节点。注意,实际文件还没有发送,只是分数
  6. 来自所有分片的分数在请求节点上合并并排序,根据查询条件选择文档
  7. 最后,从文档所在的各个分片中检索实际文档。
  8. 结果返回给客户

如果我们将此新的搜索类型应用于之前的查询,则会获得有意义的评分结果(例如,它们完全相同):

$ curl -XGET 'localhost:9200/startswith/test/_search?pretty=true&search_type=dfs_query_then_fetch' -d '{
        "query": {
        "match_phrase_prefix": {
           "title": {
             "query": "d",
             "max_expansions": 5
           }
         }
       }
     }' | grep title

      "_score" : 1.9162908, "_source" : {"title":"dzone"}
      "_score" : 1.9162908, "_source" : {"title":"data"}
      "_score" : 1.9162908, "_source" : {"title":"drunk"}
      "_score" : 1.9162908, "_source" : {"title":"drive"}

在上面我们在查询请求中使用 search_type为 dfs_query_then_fetch

  1. 预查询首先从每个分片中检索本地 IDF,以计算全局 IDF
  2. 但是,你几乎不需要在生产中使用它

结论:

当然,更好的准确性并非免费提供。 预查询会导致分片之间的额外往返,这可能会导致性能下降,具体取决于索引的大小,分片的数量,查询率等。在大多数情况下,完全没有必要……拥有“足够的”数据 为你解决问题。

但是有时你会遇到奇怪的评分情况,在这种情况下,了解如何使用 DFS 查询和获取来调整搜索执行计划很有用。

参考:

【1】Understanding "Query Then Fetch" vs "DFS Query Then Fetch" | Elastic Blog

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值