分析测试百科网

搜索

喜欢作者

微信支付微信支付
×

Elasticsearch性能优化指南(四)

2020.9.28
头像

王辉

致力于为分析测试行业奉献终身

秘密诀窍

混合精确搜索和提取词干

在构建搜索应用程序时,通常必须使用词干,比如对于“skiing”的查询需要匹配包含“ ski”或“ skis”的文档。但是,如果用户想专门搜索“skiing”怎么办?执行此操作的典型方法是使用 multi-field,以便以两种不同的方式对相同的内容建立索引:

curl -X PUT "localhost:9200/index?pretty" -H 'Content-Type: application/json' -d'{  "settings": {    "analysis": {      "analyzer": {        "english_exact": {          "tokenizer": "standard",          "filter": [            "lowercase"          ]        }      }    }  },  "mappings": {    "properties": {      "body": {        "type": "text",        "analyzer": "english",        "fields": {          "exact": {            "type": "text",            "analyzer": "english_exact"          }        }      }    }  }}'curl -X PUT "localhost:9200/index/_doc/1?pretty" -H 'Content-Type: application/json' -d'{  "body": "Ski resort"}'curl -X PUT "localhost:9200/index/_doc/2?pretty" -H 'Content-Type: application/json' -d'{  "body": "A pair of skis"}'curl -X POST "localhost:9200/index/_refresh?pretty"

两个记录都返回的搜索:

curl -X GET "localhost:9200/index/_search?pretty" -H 'Content-Type: application/json' -d'{  "query": {    "simple_query_string": {      "fields": [ "body" ],      "query": "ski"    }  }}'

返回第一个记录的搜索:

curl -X GET "localhost:9200/index/_search?pretty" -H 'Content-Type: application/json' -d'{"query": {"simple_query_string": {"fields": [ "body.exact" ],"query": "ski"    }  }}'

获得一致的评分

当要获得良好的评分功能时,Elasticsearch使用分片和副本进行操作会增加挑战。

分数不可复制

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

现在为什么会出现问题?索引统计是分数的重要组成部分。而且由于删除的文档,同一分片的副本之间的索引统计可能会有所不同。您可能知道删除或更新文档时,不会立即将旧文档从索引中删除,而是将其标记为已删除,并且仅在下次合并该旧文档所属的segment时才从磁盘中删除它。但是,出于实际原因,这些已删除的文档将用于索引统计。因此,假设主分片刚刚完成了一个大型合并,删除了许多已删除的文档,那么它的索引统计信息可能与副本(仍然有大量已删除文档)完全不同,因此得分也有所不同。

解决此问题的推荐方法是,使用一个标识所登入的用户的字符串(例如,用户ID或会话ID)作为首选项。这样可以确保给定用户的所有查询始终会打到相同的分片,因此各查询的得分更加一致。

解决此问题的另一个好处是:当两个文档的分数相同时,默认情况下将按其内部Lucene文档ID(与_id无关)对它们进行排序。但是,这些doc ID在同一分片的副本之间可能会有所不同。因此,通过始终访问相同的分片,得分相同的文档更获得一致的排序。

相关性看起来不对

如果您发现具有相同内容的两个文档获得不同的分数,或者完全匹配的内容没有排在第一位,则该问题可能与分片有关。默认情况下,Elasticsearch使每个分片负责产生自己的分数。但是,由于索引统计信息是得分的重要贡献者,因此只有在分片具有相似的索引统计信息时,此方法才有效。假设是由于默认情况下文档均匀地路由到分片,因此索引统计信息应该非常相似,并且评分将按预期进行。但是,如果您:

在写入索引时路由,

查询多个索引,

或索引中的数据太少

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

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

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

在大多数情况下,这种额外的往返开销应该很少。但是,如果您的查询包含大量字段/term或模糊查询,请注意,仅收集统计信息可能并不便利,因为必须在term词典中查找所有term才能查找到统计信息。


互联网
文章推荐