ES 多次查询结果不一致,有哪些可能?

背景

ES的搜索应用场景非常丰富,在极端情况下可能会出现同语句多次查询结果不一致的情况,特别是全文检索有打分的场景下。

一般ES如果碰到同一个语句多次查询结果不一致的情况(数量不一致/顺序不一致),一般是如下几种case:

1. DR集群数据不一致

2. 主副分片数据存储的差异(导致打分差异)

    - segment

    - deleted文件(merge独立)

    - 可查询文档(refresh独立)

    - lucene文件id

其中主副分片数据存储的差异(导致打分差异)的case也就是经典的结果震荡问题;

ES集群DR(多数据中心/多集群容灾)主要有3种方式:

- 自研跨数据中心同步方案

- 官方 CCR,收费

- 用户双写

DR集群数据不一致,肯定会对查询造成一定影响,这里不展开讨论。

结果震荡问题

场景

查询时出现“同一个DSL语句执行多次结果不一致”,如查询时返回的结果时而出现total=16,时而出现18...

分析

1. 主要原因

主分片和副本分片可能不一致,导致最终在主分片和副本分片上计算得到的得分不同,即最终的查询结果不一致。

2. 造成主分片和副本分片不一致的原因

    - 可能因为用户删除了部分文档,之后主分片进行了merge,而副本分片没有进行merge。这种情况下主分片和副本分片上的总文档数量就会不同,打分时计算出的IDF的值不同,最终得到了不同的得分。

(当文档被删除或更新时,旧文档不会立即从索引中删除,只是标记为已删除,在下次合并该旧文档所属的segment时才从磁盘中删除;假设主分片刚刚完成了一个大型merge,删除了许多已删除的文档,那么它的索引统计信息可能与副本(仍具有大量已删除文档)完全不同(副本分片不会和主分片一起进行merge操作),因此得分有所不同)

   - 如果两个文档相关性评分相同,会根据其内部Lucene文档ID对其进行排序,两个文档相同的主副分片,luceneID不一定相同,导致最后的结果不一定相同。

官方文档类似问题解释:

https://www.elastic.co/guide/en/elasticsearch/reference/6.4/consistent-scoring.html

3. 解决方案

a. 指定分片查询(查询偏好:preference)

   - 不指定分片:在所有有效的主分片以及副本间轮询

# ES6版本https://www.elastic.co/guide/en/elasticsearch/reference/6.8/search-request-preference.html`_primary`:发送到集群的相关操作请求只会在主分片上执行。`_primary_first`:查询会先在主分片中查询,如果主分片找不到(挂了),就会在副本中查询。`_replica`:发送到集群的相关操作请求只会在副本分片上执行。`_replica_first`:查询会先在副本中查询,如果副本找不到(挂了),就会在主分片中查询。
# ES7版本https://www.elastic.co/guide/en/elasticsearch/reference/7.17/search-search.html#search-preference`_local`:如果可能,请在本地节点上的分片上运行搜索。如果没有,请使用默认方法选择分片。

这里可以看到,ES7不再支持preference直接选择“_primary”。

b. 通过权重的方式增大分数差异

- 极端情况下才会出现结果震荡问题,一般情况下主副本之间带来的打分差异比较小。

- 通过权重调整,可以从业务层解决排序问题。根据权重调整打分的方式有很多种,比如function_score,这里不展开描述。

其他可能

当业务查询非常慢(响应大于1s)并且查询端设置了超时时间的时候,可能由于超时导致查询的数据不完整,多次查询极有可能数据不一致。

GET index_test/_search
{
    "timeout": "1s",
    "query": {
      "match_all": {}
    }
}

此时如果出现查询超时,结果返回将不完整,只返回固定时间内查询到的结果以及total(未查询完成的部分直接忽略),并且带有一个`timed_out=true`的标记,这也是非常容易忽视的一点。

{
  "took": 183,
  "timed_out": true,
  "_shards": {
    "total": 5,
    "successful": 5,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": 0,
    "max_score": null,
    "hits": []
  }
}

解决方案

1. 降低响应延迟

- 提升 CPU、MEM 配置 / 磁盘使用 SSD

- mapping 优化,ES7之后字段类型的type会较大影响查询性能

- 查询语句优化

查询语句优化的定制化要求比较高:

比如我们遇到的是大量数据(亿级文档数)聚合比较慢,优化主要通过:

- 不使用序数 "execution_hint": "map"- 在查询之前提前创建好全局序数 "eager_global_ordinals": true

2. 增加超时时间

GET index_test/_search
{
    "timeout": "5s",
    "query": {
      "match_all": {}
    }
}

  • 14
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
如果利用 `from` 和 `size` 进行分页查询可能会出现数据一致的情况。这是因为在 Elasticsearch 中,数据是分布式存储的,而分页查询是基于分片进行的。当一个查询跨越多个分片时,由于不同分片数据的不同,可能会导致数据一致的情况。 解决这个问题的方法是,使用 Search After API 来进行分页查询。Search After API 可以在查询结果中保留一个游标,可以在下一次查询中使用该游标来继续查询,从而确保数据一致性。 具体的做法是,在第一次查询时,使用 `sort` 参数对结果进行排序,并记录最后一条结果的排序值(可以是 `_id` 或其他字段)。在下一次查询时,使用 `search_after` 参数来指定排序值,以继续查询下一页数据。这样就可以确保每一页的数据是连续的,不会出现重复或遗漏的情况。 以下是使用 Search After API 进行分页查询的示例代码: ``` SearchRequest searchRequest = new SearchRequest("index_name"); SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); searchSourceBuilder.query(QueryBuilders.matchAllQuery()); searchSourceBuilder.sort("_id", SortOrder.ASC); searchSourceBuilder.size(pageSize); if (page > 1) { // 使用 search_after 参数来指定排序值,以继续查询下一页 searchSourceBuilder.searchAfter(lastSortValues); } searchRequest.source(searchSourceBuilder); SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT); SearchHits hits = searchResponse.getHits(); if (hits.getTotalHits().value > 0) { SearchHit[] searchHits = hits.getHits(); for (SearchHit hit : searchHits) { // 处理查询结果 } // 记录最后一条结果的排序值,以供下一页查询使用 lastSortValues = searchHits[searchHits.length - 1].getSortValues(); } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值