获得一致的评分编辑

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

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