获得一致的评分

编辑

Elasticsearch 使用分片和副本的事实,在获得良好评分方面增加了一些挑战。

评分不可重现

编辑

假设同一个用户连续两次运行相同的请求,并且文档两次返回的顺序不同,这不是一个非常糟糕的体验吗?不幸的是,如果您有副本(index.number_of_replicas 大于 0),这种情况可能会发生。原因是 Elasticsearch 以循环方式选择查询应该发送到的分片,因此如果您连续运行相同的查询两次,很可能会发送到同一个分片的不同副本。

现在为什么这是一个问题呢?索引统计信息是评分的重要组成部分。并且由于已删除的文档,这些索引统计信息在同一分片的副本之间可能会有所不同。您可能知道,当文档被删除或更新时,旧文档不会立即从索引中删除,它只是被标记为已删除,并且只有在下次合并此旧文档所属的段时才会从磁盘中删除。然而,出于实际原因,这些已删除的文档会被纳入索引统计信息。因此,想象一下,主分片刚刚完成了一个大型合并,删除了大量已删除的文档,那么它的索引统计信息可能与副本(仍然有大量已删除的文档)有很大的不同,因此评分也会不同。

解决此问题的推荐方法是使用一个字符串来标识已登录的用户(例如用户 ID 或会话 ID)作为 偏好。这确保了给定用户的所有查询始终会命中相同的分片,因此评分在查询之间保持更加一致。

这种解决方法还有另一个好处:当两个文档具有相同的评分时,默认情况下它们将按其内部 Lucene 文档 ID(与 _id 无关)排序。然而,这些文档 ID 在同一分片的副本之间可能会有所不同。因此,通过始终命中相同的分片,我们将获得具有相同评分的文档更一致的排序。

相关性看起来错误

编辑

如果您注意到两个内容相同的文档获得不同的评分,或者精确匹配没有排在第一位,那么问题可能与分片有关。默认情况下,Elasticsearch 使每个分片负责生成自己的评分。然而,由于索引统计信息是评分的重要贡献者,因此只有当分片具有相似的索引统计信息时,此方法才能正常工作。假设默认情况下文档均匀地路由到分片,那么索引统计信息应该非常相似,并且评分会按预期工作。然而,如果您

  • 在索引时使用路由,
  • 查询多个索引
  • 或者索引中的数据太少

那么很有可能搜索请求中涉及的所有分片都没有相似的索引统计信息,并且相关性可能会很差。

如果您有一个小型数据集,解决此问题的最简单方法是将所有内容索引到具有单个分片的索引中(index.number_of_shards: 1),这是默认设置。然后,所有文档的索引统计信息将相同,并且评分将保持一致。

否则,解决此问题的推荐方法是使用 dfs_query_then_fetch 搜索类型。这将使 Elasticsearch 执行到所有相关分片的初始往返,要求它们提供相对于查询的索引统计信息,然后协调节点将合并这些统计信息,并在要求分片执行 query 阶段时,将合并的统计信息与请求一起发送,以便分片可以使用这些全局统计信息而不是它们自己的统计信息来进行评分。

在大多数情况下,这个额外的往返应该非常便宜。然而,如果您的查询包含大量字段/术语或模糊查询,请注意,仅收集统计信息可能并不便宜,因为必须在术语字典中查找所有术语才能查找统计信息。