Elasticsearch 之分页查询

Elasticsearch 之分页查询

引言

在搜索引擎应用中,分页查询是一种常见需求。Elasticsearch 提供了多种分页方式以应对不同场景。本文将结合实际应用场景,介绍三种常用的分页查询方法

From + Size 分页查询

json 复制代码
GET /content_item_profile/_search
{
  "from": 0,
  "size":20,
  "query": {
    "match": {
      "title": "字节跳动"
    }
  },
  "sort": [
    {
      "born_time": {
        "order": "desc"
      }
    }
  ]
}
  • 上述 ES 查询语句共返回20条结果
  • From + Size 查询的优缺点
    • 优点: 支持随机翻页
    • 缺点
      • 受制于 max_result_window 设置,不能无限制翻页
      • 存在深度翻页问题,越往后翻页越慢
      • 不推荐使用 from + size 做深度分页查询的核心原因
        • 搜索请求通常跨越多个分片,每个分片必须将其请求的命中内容以及任何先前页面的命中内容加载到内存中
        • CPU: 每个分片和协调节点都需要进行大规模排序,CPU压力巨大
        • 内存:协调节点需要创建一个容量为 from + size的优先级队列来存储和排序所有分片的结果,大量消耗堆内存
        • 带宽:分片和协调节点之间需要传输大量数据,占用网络带宽
        • 响应时间:整个过程非常缓慢,延迟可能从毫秒级变为秒级甚至分钟级
  • From + Size 分页查询适用场景
    • 小型数据集
    • 搜索引擎随机跳转分页的业务场景

search_after 查询

search_after 避免使用昂贵的 from,它使用上一页最后一个结果的排序值作为起点来获取下一页。查询时需要指定上一页最后一个文档的排序值。ES 创建一个时间点 Point In Time(PIT)保障搜索过程中保留特定事件点的索引状态。search_after 分页查询可以简单概括为如下几个步骤

  • 步骤 1:创建 PIT 视图
json 复制代码
POST /job_item_profile_prod20240112181802/_pit?keep_alive=1m

返回结果:

json 复制代码
{
  "id" : "64O1AwMjam9iX2l0ZW1fcHJvZmlsZV9wcm9kMjAyNDAxMTIxODE4MDIWMHpKY3VEanJTYnU4dXBpTGhLZjd2ZwAWTDhJS1lfME1SRUdfNldWMUIwZlNFQQAAAAAAFd1wmxZEMFZYb1RudlJIaS1seW9PWTdyZktnACNqb2JfaXRlbV9wcm9maWxlX3Byb2QyMDI0MDExMjE4MTgwMhYwekpjdURqclNidTh1cGlMaEtmN3ZnARZuSHhjd1hQRlRlV1ZPVzFlVDZmSXJBAAAAAAAXrPHvFlFpelRRcEJsUXlXZXg4VTVEZGJ6VFEAI2pvYl9pdGVtX3Byb2ZpbGVfcHJvZDIwMjQwMTEyMTgxODAyFjB6SmN1RGpyU2J1OHVwaUxoS2Y3dmcCFm02TVZRNGl4U2NxY0hpeTlQaFF3NHcAAAAAABbwjhcWUllXVWJWLXNUMDIxZnJCbGJhb3dqdwABFjB6SmN1RGpyU2J1OHVwaUxoS2Y3dmcAAA=="
}

获取数据量

json 复制代码
POST /job_item_profile_prod20240112181802/_count

返回结果:

json 复制代码
{
  "count" : 337562,
  "_shards" : {
    "total" : 3,
    "successful" : 3,
    "skipped" : 0,
    "failed" : 0
  }
}

keep_alive=1m,代表视图保留时间是1分钟,超过1分钟执行会报错如下

json 复制代码
{
  "error" : {
    "root_cause" : [
      {
        "type" : "search_context_missing_exception",
        "reason" : "No search context found for id [384877637]"
      }
      ]
    },
    "status" : 404
  • 步骤 2:创建基础查询语句,这里要设置翻页的条件
json 复制代码
GET /_search
{
  "size":10,
  "query": {
    "match" : {
      "job_name" : "java"
    }
  },
  "pit": {
     "id":  "64O1AwMjam9iX2l0ZW1fcHJvZmlsZV9wcm9kMjAyNDAxMTIxODE4MDIWMHpKY3VEanJTYnU4dXBpTGhLZjd2ZwAWbTZNVlE0aXhTY3FjSGl5OVBoUXc0dwAAAAAAFvDERRZSWVdVYlYtc1QwMjFmckJsYmFvd2p3ACNqb2JfaXRlbV9wcm9maWxlX3Byb2QyMDI0MDExMjE4MTgwMhYwekpjdURqclNidTh1cGlMaEtmN3ZnARZMOElLWV8wTVJFR182V1YxQjBmU0VBAAAAAAAV3Z7zFkQwVlhvVG52UkhpLWx5b09ZN3JmS2cAI2pvYl9pdGVtX3Byb2ZpbGVfcHJvZDIwMjQwMTEyMTgxODAyFjB6SmN1RGpyU2J1OHVwaUxoS2Y3dmcCFm02TVZRNGl4U2NxY0hpeTlQaFF3NHcAAAAAABbwxEYWUllXVWJWLXNUMDIxZnJCbGJhb3dqdwABFjB6SmN1RGpyU2J1OHVwaUxoS2Y3dmcAAA==", 
     "keep_alive": "1m"
  },
  "sort": [ 
    {"edu_level": "asc"}
  ]
}

PIT 和索引不能同时使用,设置了PIT,检索时候就不需要再指定索引,两者一起使用会报下面的错误

json 复制代码
{
  "error" : {
    "root_cause" : [
      {
        "type" : "action_request_validation_exception",
        "reason" : "Validation Failed: 1: [indices] cannot be used with point in time. Do not specify any index with point in time.;"
      }
    ],
    "type" : "action_request_validation_exception",
    "reason" : "Validation Failed: 1: [indices] cannot be used with point in time. Do not specify any index with point in time.;"
  },
  "status" : 400
}

在每个返回文档的最后,sort 会有两个结果值,如下所示:

json 复制代码
{
  "pit_id" : "64O1AwMjam9iX2l0ZW1fcHJvZmlsZV9wcm9kMjAyNDAxMTIxODE4MDIWMHpKY3VEanJTYnU4dXBpTGhLZjd2ZwAWTDhJS1lfME1SRUdfNldWMUIwZlNFQQAAAAAAFd20khZEMFZYb1RudlJIaS1seW9PWTdyZktnACNqb2JfaXRlbV9wcm9maWxlX3Byb2QyMDI0MDExMjE4MTgwMhYwekpjdURqclNidTh1cGlMaEtmN3ZnARZuSHhjd1hQRlRlV1ZPVzFlVDZmSXJBAAAAAAAXrUI0FlFpelRRcEJsUXlXZXg4VTVEZGJ6VFEAI2pvYl9pdGVtX3Byb2ZpbGVfcHJvZDIwMjQwMTEyMTgxODAyFjB6SmN1RGpyU2J1OHVwaUxoS2Y3dmcCFm02TVZRNGl4U2NxY0hpeTlQaFF3NHcAAAAAABbw3WUWUllXVWJWLXNUMDIxZnJCbGJhb3dqdwABFjB6SmN1RGpyU2J1OHVwaUxoS2Y3dmcAAA==",
  "took" : 15,
  "timed_out" : false,
  "_shards" : {
    "total" : 3,
    "successful" : 3,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 10000,
      "relation" : "gte"
    },
    "max_score" : null,
    "hits" : [
      {
        "_index" : "job_item_profile_prod20240112181802",
        "_type" : "_doc",
        "_id" : "658a3a55aec19df69534f217",
        "_score" : null,
        "_source" : {
          "id" : 30100000262389,
          ...
        },
        "sort" : [
          0,
          94
        ]
      },
      ...
  • 返回的 sort数组 [0, 94]分别代表
    • 0: 这是当前文档 _source.edu_level字段的排序值。从 _source里可以看到 "edu_level": 0,所以这里就是 0
    • 94 : 这是当前文档 _id字段 (658a3a55aec19df69534f217) 的内部排序表示。Elasticsearch 不会直接返回字符串形式的 _id用于 search_after,而是会将其转换成一个更高效的、可用于比较的数字或哈希值。94就是这个内部值
      • Note: Elasticsearch 有一个内置的保障机制:为了确保排序结果的唯一性和分页的准确性,如果提供的排序条件不足以唯一确定所有文档的顺序,它会自动添加一个隐式的、基于文档 _id的排序条件作为最终决胜局(Tiebreaker)

步骤3:实现后续翻页

json 复制代码
GET /_search
{
  "size": 10,
  "query": {
    "match": {
      "job_name": "java"
    }
  },
  "pit": {
    "id": "64O1AwMjam9iX2l0ZW1fcHJvZmlsZV9wcm9kMjAyNDAxMTIxODE4MDIWMHpKY3VEanJTYnU4dXBpTGhLZjd2ZwAWTDhJS1lfME1SRUdfNldWMUIwZlNFQQAAAAAAFd20khZEMFZYb1RudlJIaS1seW9PWTdyZktnACNqb2JfaXRlbV9wcm9maWxlX3Byb2QyMDI0MDExMjE4MTgwMhYwekpjdURqclNidTh1cGlMaEtmN3ZnARZuSHhjd1hQRlRlV1ZPVzFlVDZmSXJBAAAAAAAXrUI0FlFpelRRcEJsUXlXZXg4VTVEZGJ6VFEAI2pvYl9pdGVtX3Byb2ZpbGVfcHJvZDIwMjQwMTEyMTgxODAyFjB6SmN1RGpyU2J1OHVwaUxoS2Y3dmcCFm02TVZRNGl4U2NxY0hpeTlQaFF3NHcAAAAAABbw3WUWUllXVWJWLXNUMDIxZnJCbGJhb3dqdwABFjB6SmN1RGpyU2J1OHVwaUxoS2Y3dmcAAA==",
    "keep_alive": "1m"
  },
  "sort": [
    {"edu_level": "asc"}
  ],
  "search_after": [0, 94] 
}

后续翻页都可以借助 search_after 指定前一页的最后一个文档的 sort 字段值

  • search_after 查询的优缺点
    • 优点:不严格受制于 max_result_window,可以无限制往后翻页
    • 缺点:只支持向后翻页,不支持随机翻页
  • search_after 分页查询适用场景
    • 适合手机端搜索,下拉刷新式的翻页

Scroll 遍历查询

scroll 支持全量遍历,详细操作可以参阅 Elasticsearch scroll 之滚动查询~

  • Scroll 查询的优缺点
    • 优点:支持全量遍历
    • 缺点
      • 单次遍历的 size 值也不能超过 max_result_window 大小
      • 保留上下文需要足够的堆内存空间
      • 响应时间非实时
    • Note: Scroll 为旧版全量遍历方式。ES 官方推荐的现代方式是 search_after + PIT 的方式来进行全量遍历

参考文献

1\] Elasticsearch最佳生产实践,推荐收藏! \[2\] ElasticSearch三种分页方式以及各优缺点. \[3\] Elasticsearch scroll 之滚动查询.

相关推荐
ACGkaka_3 小时前
ES 学习(九)从文本到词元:分词器如何“拆解“你的数据
大数据·学习·elasticsearch
Elastic 中国社区官方博客4 小时前
如何使用 LogsDB 降低 Elasticsearch 日志存储成本
大数据·运维·数据库·elasticsearch·搜索引擎·全文检索·可用性测试
A__tao4 小时前
一键实现 SQL 转 Elasticsearch Mapping(支持字段注释 + meta 描述)
数据库·sql·elasticsearch
老纪的技术唠嗑局1 天前
告别OpenClaw配置丢失——Mindkeeper内测版邀测
大数据·elasticsearch·搜索引擎
Elasticsearch1 天前
使用 Elasticsearch + Jina embeddings 进行无监督文档聚类
elasticsearch
勇哥的编程江湖1 天前
flinkcdc streaming 同步数据到es记录过程
大数据·elasticsearch·flink·flinkcdc
曾阿伦1 天前
Elasticsearch 7.x 常用命令备忘录
大数据·elasticsearch·搜索引擎
斯特凡今天也很帅1 天前
Elasticsearch数据库专栏(二)DSL语句总结(更新中)
大数据·elasticsearch·搜索引擎
要记得喝水1 天前
适用于 Git Bash 的脚本,批量提交和推送多个仓库的修改
git·elasticsearch·bash
二十七剑1 天前
Elasticsearch的索引问题
大数据·elasticsearch·搜索引擎