【Elastic Engineering】Elasticsearch:分页搜索结果

简介: Elasticsearch:分页搜索结果

作者:刘晓国


在我之前的文章:


Elasticsearch:运用 scroll 接口对大量数据实现更好的分页

Elasticsearch:运用 search_after 来进行深度分页


我讲述了如何针对大量搜索结果进行分页的描述。随着时间点 API(Point in time API)的推出,根据 Elastic 的官方博客 “使用 Elasticsearch 时间点读取器获得随时间推移而保持一致的数据视图”,Scroll 接口将不被推荐作为对搜索结果的分页。


默认情况下,搜索会返回前 10 个匹配的匹配项。 要翻阅更大的结果集,你可以使用搜索 API 的 from 和 size 参数。 from 参数定义要跳过的命中数,默认为 0。 size 参数是要返回的最大命中数。 这两个参数共同定义了一页结果。比如:

GET /twitter/_search
{
  "from": 5,
  "size": 20,
  "query": {
    "match": {
      "city": "北京"
    }
  }
}

避免使用 from 和 size 来分页太深或一次请求太多结果。 搜索请求通常跨越多个分片。 每个分片必须将其请求的命中和任何先前页面的命中加载到内存中。 对于深页面或大型结果集,这些操作会显着增加内存和 CPU 使用率,从而导致性能下降或节点故障。这里的原因是 index.max_result_window 的默认值是 10K,也就是说 from+size 的最大值是1万。搜索请求占用堆内存和时间与 from+size 成比例,这限制了内存。假如你想 hit 从 990 到 1000,那么每个 shard 至少需要 1000 个文档:

image.png

默认情况下,你不能使用 from 和 size 来翻阅超过 10,000 次点击。 此限制是由 index.max_result_window 索引设置设置的保护措施。 如果你需要翻阅超过 10,000 次点击,请改用 search_after 参数。


警告:Elasticsearchimage.png使用 Lucene 的内部文档 ID 作为 tie_breaker。 这些内部文档 ID 可以在相同数据的副本之间完全不同。 当分页搜索命中时,你可能偶尔会看到具有相同排序值的文档排序不一致。


Search after


你可以使用 search_after 参数使用上一页中的一组 sort values 来检索下一页的命中。


使用 search_after 需要具有相同查询和排序值的多个搜索请求。 如果在这些请求之间发生刷新,结果的顺序可能会发生变化,从而导致跨页面的结果不一致。 为防止出现这种情况,你可以创建一个时间点 (PIT) 以保留搜索中的当前索引状态。

POST /my-index-000001/_pit?keep_alive=1m

上面的命令返回一个 PIT id:

{
  "id": "46ToAwMDaWR5BXV1aWQyKwZub2RlXzMAAAAAAAAAACoBYwADaWR4BXV1aWQxAgZub2RlXzEAAAAAAAAAAAEBYQADaWR5BXV1aWQyKgZub2RlXzIAAAAAAAAAAAwBYgACBXV1aWQyAAAFdXVpZDEAAQltYXRjaF9hbGw_gAAAAA=="
}

要获取第一页结果,请提交带有排序参数的搜索请求。 如果使用 PIT,请在 pit.id 参数中指定 PIT id,并从请求路径中省略目标数据流或索引。


重要:所有 PIT 搜索请求都添加了一个名为 _shard_doc 的隐式排序 tiebreaker 字段,该字段也可以显式提供。 如果 你不能使用 PIT,我们建议你在排序中包含一个 tiebreaker 字段。 此 tiebreaker 字段应包含每个文档的唯一值。 如果你不包含 tiebreaker 字段,你的分页结果可能会丢失或重复命中。


注意:当排序顺序为 _shard_doc 且未跟踪总命中数(total hits)时,请求后搜索进行了优化,使它们更快。 如果你想遍历所有文档而不考虑顺序,这是最有效的选择。


重要:如果排序字段在某些目标数据流或索引中是 date,但在其他目标中是 date_nanos 字段,请使用 numeric_type 参数将值转换为单一分辨率,并使用 format 参数为排序字段指定日期格式。 否则,Elasticsearch 将无法正确解释每个请求中的 search after 参数。

GET /_search
{
  "size": 10000,
  "query": {
    "match": {
      "user.id": "elkbee"
    }
  },
  "pit": {
    "id": "46ToAwMDaWR5BXV1aWQyKwZub2RlXzMAAAAAAAAAACoBYwADaWR4BXV1aWQxAgZub2RlXzEAAAAAAAAAAAEBYQADaWR5BXV1aWQyKgZub2RlXzIAAAAAAAAAAAwBYgACBXV1aWQyAAAFdXVpZDEAAQltYXRjaF9hbGw_gAAAAA==",
    "keep_alive": "1m"
  },
  "sort": [
    {
      "@timestamp": {
        "order": "asc",
        "format": "strict_date_optional_time_nanos",
        "numeric_type": "date_nanos"
      }
    }
  ]
}

在上面,我们使用 pit.id 来进行搜索。在 sort 里,在 _shard_doc 升序上使用隐式 tiebreaker 对搜索的命中进行排序。


搜索响应包括每个命中的 sort 值数组。 如果你使用了 PIT,则将包含一个 tiebreaker 作为每个命中的最后一个排序值。 这个名为 _shard_doc 的 tiebreaker 会自动添加到使用 PIT 的每个搜索请求中。 _shard_doc 值是 PIT 中的分片索引和 Lucene 的内部文档 ID 的组合,它在每个文档中是唯一的,并且在 PIT 中是常量。 你还可以在搜索请求中显式添加 tiebreaker 以自定义顺序:

GET /_search
{
  "size": 10000,
  "query": {
    "match": {
      "user.id": "elkbee"
    }
  },
  "pit": {
    "id": "46ToAwMDaWR5BXV1aWQyKwZub2RlXzMAAAAAAAAAACoBYwADaWR4BXV1aWQxAgZub2RlXzEAAAAAAAAAAAEBYQADaWR5BXV1aWQyKgZub2RlXzIAAAAAAAAAAAwBYgACBXV1aWQyAAAFdXVpZDEAAQltYXRjaF9hbGw_gAAAAA==",
    "keep_alive": "1m"
  },
  "sort": [
    {
      "@timestamp": {
        "order": "asc",
        "format": "strict_date_optional_time_nanos"
      }
    },
    {
      "_shard_doc": "desc"
    }
  ]
}

在上面,我们是有 pit.id 来进行搜索。同时,我们在 _shard_doc 降序上使用显式 tiebreaker 对搜索进行排序。

{
  "pit_id" : "46ToAwMDaWR5BXV1aWQyKwZub2RlXzMAAAAAAAAAACoBYwADaWR4BXV1aWQxAgZub2RlXzEAAAAAAAAAAAEBYQADaWR5BXV1aWQyKgZub2RlXzIAAAAAAAAAAAwBYgACBXV1aWQyAAAFdXVpZDEAAQltYXRjaF9hbGw_gAAAAA==", 
  "took" : 17,
  "timed_out" : false,
  "_shards" : ...,
  "hits" : {
    "total" : ...,
    "max_score" : null,
    "hits" : [
      ...
      {
        "_index" : "my-index-000001",
        "_id" : "FaslK3QBySSL_rrj9zM5",
        "_score" : null,
        "_source" : ...,
        "sort" : [                                
          "2021-05-20T05:30:04.832Z",
          4294967298                              
        ]
      }
    ]
  }
}

从上面的返回结果中,我们可以看出来一个被更新的 pit.id。在 sort 里,它定义了最近返回命中的 sort 值。上面的 4294967298 是一个 tiebreaker 值。在 pit_id 中每个文档都是唯一的。


要获得下一页结果,请使用最后一次命中的排序值(包括 tiebreaker)作为 search_after 参数重新运行先前的搜索。 如果使用 PIT,请在 pit.id 参数中使用最新的 PIT ID。 搜索的查询和排序参数必须保持不变。 如果提供,则 from 参数必须为 0(默认值)或 -1。

GET /_search
{
  "size": 10000,
  "query": {
    "match": {
      "user.id": "elkbee"
    }
  },
  "pit": {
    "id": "46ToAwMDaWR5BXV1aWQyKwZub2RlXzMAAAAAAAAAACoBYwADaWR4BXV1aWQxAgZub2RlXzEAAAAAAAAAAAEBYQADaWR5BXV1aWQyKgZub2RlXzIAAAAAAAAAAAwBYgACBXV1aWQyAAAFdXVpZDEAAQltYXRjaF9hbGw_gAAAAA==",
    "keep_alive": "1m"
  },
  "sort": [
    {
      "@timestamp": {
        "order": "asc",
        "format": "strict_date_optional_time_nanos"
      }
    }
  ],
  "search_after": [
    "2021-05-20T05:30:04.832Z",
    4294967298
  ],
  "track_total_hits": false
}


请注意:


在上面的请求中,pit.id 是上一个请求返回来的 pit.id 值

在 search_after 里定义的是是上一次请求最后一个命中返回的 sort 值

我们在这个请求中把 track_total_hits 设置为 false 来 禁用对总点击数的跟踪以加快分页速度

你可以重复此过程以获取其他页面的结果。 如果使用 PIT,你可以使用每个搜索请求的 keep_alive 参数延长 PIT 的保留期。


完成后,你应该删除 PIT。

DELETE /_pit
{
    "id" : "46ToAwMDaWR5BXV1aWQyKwZub2RlXzMAAAAAAAAAACoBYwADaWR4BXV1aWQxAgZub2RlXzEAAAAAAAAAAAEBYQADaWR5BXV1aWQyKgZub2RlXzIAAAAAAAAAAAwBYgACBXV1aWQyAAAFdXVpZDEAAQltYXRjaF9hbGw_gAAAAA=="
}


参考:


【1】https://www.elastic.co/guide/en/elasticsearch/reference/current/paginate-search-results.html

相关实践学习
以电商场景为例搭建AI语义搜索应用
本实验旨在通过阿里云Elasticsearch结合阿里云搜索开发工作台AI模型服务,构建一个高效、精准的语义搜索系统,模拟电商场景,深入理解AI搜索技术原理并掌握其实现过程。
ElasticSearch 最新快速入门教程
本课程由千锋教育提供。全文搜索的需求非常大。而开源的解决办法Elasricsearch(Elastic)就是一个非常好的工具。目前是全文搜索引擎的首选。本系列教程由浅入深讲解了在CentOS7系统下如何搭建ElasticSearch,如何使用Kibana实现各种方式的搜索并详细分析了搜索的原理,最后讲解了在Java应用中如何集成ElasticSearch并实现搜索。  
相关文章
|
6月前
|
缓存 监控 前端开发
顺企网 API 开发实战:搜索 / 详情接口从 0 到 1 落地(附 Elasticsearch 优化 + 错误速查)
企业API开发常陷参数、缓存、错误处理三大坑?本指南拆解顺企网双接口全流程,涵盖搜索优化、签名验证、限流应对,附可复用代码与错误速查表,助你2小时高效搞定开发,提升响应速度与稳定性。
|
6月前
|
存储 Linux iOS开发
Elasticsearch Enterprise 9.1.5 发布 - 分布式搜索和分析引擎
Elasticsearch Enterprise 9.1.5 (macOS, Linux, Windows) - 分布式搜索和分析引擎
494 0
|
7月前
|
JSON 监控 Java
Elasticsearch 分布式搜索与分析引擎技术详解与实践指南
本文档全面介绍 Elasticsearch 分布式搜索与分析引擎的核心概念、架构设计和实践应用。作为基于 Lucene 的分布式搜索引擎,Elasticsearch 提供了近实时的搜索能力、强大的数据分析功能和可扩展的分布式架构。本文将深入探讨其索引机制、查询 DSL、集群管理、性能优化以及与各种应用场景的集成,帮助开发者构建高性能的搜索和分析系统。
510 0
|
11月前
|
存储 安全 Linux
Elasticsearch Enterprise 9.0 发布 - 分布式搜索和分析引擎
Elasticsearch Enterprise 9.0 (macOS, Linux, Windows) - 分布式搜索和分析引擎
464 0
|
11月前
|
存储 Linux iOS开发
Elasticsearch Enterprise 8.18 发布 - 分布式搜索和分析引擎
Elasticsearch Enterprise 8.18 (macOS, Linux, Windows) - 分布式搜索和分析引擎
412 0
|
数据采集 人工智能 运维
从企业级 RAG 到 AI Assistant,阿里云Elasticsearch AI 搜索技术实践
本文介绍了阿里云 Elasticsearch 推出的创新型 AI 搜索方案
861 3
从企业级 RAG 到 AI Assistant,阿里云Elasticsearch AI 搜索技术实践
|
人工智能 自然语言处理 搜索推荐
云端问道12期实操教学-构建基于Elasticsearch的企业级AI搜索应用
本文介绍了构建基于Elasticsearch的企业级AI搜索应用,涵盖了从传统关键词匹配到对话式问答的搜索形态演变。阿里云的AI搜索产品依托自研和开源(如Elasticsearch)引擎,提供高性能检索服务,支持千亿级数据毫秒响应。文章重点描述了AI搜索的三个核心关键点:精准结果、语义理解、高性能引擎,并展示了架构升级和典型应用场景,包括智能问答、电商导购、多模态图书及商品搜索等。通过实验部分,详细演示了如何使用阿里云ES搭建AI语义搜索Demo,涵盖模型创建、Pipeline配置、数据写入与检索测试等步骤,同时介绍了相关的计费模式。
457 3
|
人工智能 算法 API
构建基于 Elasticsearch 的企业级 AI 搜索应用
本文介绍了基于Elasticsearch构建企业级AI搜索应用的方案,重点讲解了RAG(检索增强生成)架构的实现。通过阿里云上的Elasticsearch AI搜索平台,简化了知识库文档抽取、文本切片等复杂流程,并结合稠密和稀疏向量的混合搜索技术,提升了召回和排序的准确性。此外,还探讨了Elastic的向量数据库优化措施及推理API的应用,展示了如何在云端高效实现精准的搜索与推理服务。未来将拓展至多模态数据和知识图谱,进一步提升RAG效果。
557 1
|
数据采集 人工智能 运维
从企业级 RAG 到 AI Assistant,阿里云Elasticsearch AI 搜索技术实践
本文介绍了阿里云 Elasticsearch 推出的创新型 AI 搜索方案。
1078 5
|
搜索推荐 API 定位技术
一文看懂Elasticsearch的技术架构:高效、精准的搜索神器
Elasticsearch 是一个基于 Lucene 的开源搜索引擎,以其强大的全文本搜索功能和快速的倒排索引技术著称。它不仅支持数字、文本、地理位置等多类型数据,还提供了可调相关度分数、高级查询 DSL 等功能。Elasticsearch 的核心技术流程包括数据导入、解析、索引化、查询处理、得分计算及结果返回,确保高效处理大规模数据并提供准确的搜索结果。通过 RESTful API、Logstash 和 Filebeat 等工具,Elasticsearch 可以从多种数据源中导入和解析数据,支持复杂的查询需求。
817 0

热门文章

最新文章

相关产品

  • 检索分析服务 Elasticsearch版