获取一致的评分

编辑

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阶段时将合并的统计信息与请求一起发送,以便分片可以使用这些全局统计信息而不是它们自己的统计信息来进行评分。

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