elasticsearch 深分页问题

Elasticsearch 的深分页问题与 MySQL 的深分页类似。深分页指的是在查询大量数据时,跳过很多条记录才能获取目标页的结果,从而导致性能显著下降。在 Elasticsearch 中,当使用 fromsize 参数进行分页查询时,深分页会造成大量的资源消耗,包括内存和 CPU,从而导致查询性能下降,甚至可能导致节点内存溢出。

一、Elasticsearch 深分页问题的原因分析

  1. from 和 size 参数的工作机制
    • from 参数用于指定跳过的记录数,size 参数指定返回的记录数。对于深分页(例如 from=100000size=10)的请求,Elasticsearch 需要读取、过滤和排序大量的文档,即使最终只返回少量结果。
    • from 值很大时,Elasticsearch 会扫描和排序大量文档,并将它们加载到内存中,然后跳过指定数量的文档,直到返回所需的 size 条结果。
  2. 内存消耗
    • Elasticsearch 的分页查询会将文档加载到内存中,并使用优先队列对文档进行排序。当 from 值较大时,需要将更多的文档加载到内存并排序,导致内存使用量激增。
    • 深分页操作可能会占用大量堆内存,导致 OutOfMemoryError,从而使 Elasticsearch 节点宕机。
  3. 性能瓶颈
    • 当查询需要处理大量数据时,CPU 和 I/O 负载会显著增加,影响整个集群的查询性能。
    • 当并发深分页请求增多时,集群的资源耗尽,响应时间急剧上升,影响其他正常请求的处理。

二、Elasticsearch 深分页的解决方案

为了解决 Elasticsearch 的深分页问题,可以采取以下几种优化方案:

方案 1:使用 search_after 进行滚动分页

search_after 是一种基于排序的分页方法,适合深度分页。search_after 不依赖 from 跳过文档,而是使用上一页最后一条记录的排序值来定位下一页的起点,因此避免了扫描和跳过大量记录。

示例

假设我们按 id 字段进行排序,并获取第一页结果:

jsonCopy codeGET /employees/_search
{
  "size": 10,
  "sort": [{ "id": "asc" }]
}

返回结果后,将最后一条记录的 id 作为 search_after 参数的值,继续请求下一页:

jsonCopy codeGET /employees/_search
{
  "size": 10,
  "sort": [{ "id": "asc" }],
  "search_after": [last_id]
}

原理search_after 基于上一页最后一个文档的排序值来定位下一页的起点,无需跳过大量文档,显著减少了深分页的内存消耗。

优缺点

  • 优点:适合大数据集的深分页,减少内存和 CPU 消耗。
  • 缺点:需要按排序字段获取上一页的最后一个值,且只能顺序分页,无法随机跳转到特定页。

方案 2:使用 scroll 实现游标分页

scroll 是 Elasticsearch 提供的滚动分页机制,适合处理超大量数据的批量读取。scroll 查询会生成一个游标(cursor),用于保持查询的上下文,避免重复扫描和跳过大量数据。

示例

首先创建一个滚动上下文(游标),获取第一页:

jsonCopy codeGET /employees/_search?scroll=1m
{
  "size": 1000,
  "query": { "match_all": {} }
}

返回的结果中包含一个 _scroll_id,使用该 _scroll_id 获取下一批数据:

jsonCopy codeGET /_search/scroll
{
  "scroll": "1m",
  "scroll_id": "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAsQ1WbmlOV1FpN2h2SEZnQ0U5b0haU3c"
}

原理scroll 查询将初始查询的结果快照保存下来,并根据快照依次读取分页结果,避免重复的查询和排序计算。

优缺点

  • 优点:适合大规模数据的批量读取,性能稳定。
  • 缺点:scroll 不适合实时分页查询,因为数据快照在整个查询过程中保持不变,不反映实时数据更新。使用完后需及时释放 scroll 资源。

方案 3:使用 search_after + 分页 ID 或者分区键的组合方式

对于需要深分页的实时数据,可以根据业务需求分区数据。例如,先按某个分区键(如日期、类别)过滤数据,然后结合 search_after 滚动分页查询,减少每次查询的数据量。

示例

假设需要查询员工表中不同部门的员工列表,可以先按 department 过滤,再使用 search_after 滚动分页:

jsonCopy codeGET /employees/_search
{
  "query": { "term": { "department": "sales" } },
  "size": 10,
  "sort": [{ "id": "asc" }]
}

原理: 通过分区过滤,将大数据量分散到不同分区进行分页,避免深分页时的全量扫描。然后结合 search_after 实现滚动分页。

优缺点

  • 优点:减少每次查询的文档数量,适合对实时数据进行分区。
  • 缺点:需要对业务数据进行分区和过滤,适用性受限。

方案 4:替代分页,使用 composite aggregation 分页聚合

在聚合查询场景下,composite aggregation 可以替代传统分页。composite aggregation 支持深度分页,且性能较稳定,适用于聚合查询。

示例

department 字段分页聚合,获取每页聚合结果:

jsonCopy codeGET /employees/_search
{
  "size": 0,
  "aggs": {
    "departments": {
      "composite": {
        "size": 10,
        "sources": [
          { "department": { "terms": { "field": "department" } } }
        ]
      }
    }
  }
}

在第一次请求返回的结果中包含 after_key 值,用于请求下一页:

jsonCopy codeGET /employees/_search
{
  "size": 0,
  "aggs": {
    "departments": {
      "composite": {
        "size": 10,
        "sources": [
          { "department": { "terms": { "field": "department" } } }
        ],
        "after": { "department": "sales" }  -- 下一页起点
      }
    }
  }
}

原理composite aggregation 使用 after 参数来定位下一页的起点,避免了大数据量聚合查询时的内存消耗问题。

优缺点

  • 优点:适用于聚合场景,性能稳定,支持深分页。
  • 缺点:只能用于聚合查询,适用场景较为有限。

三、各解决方案的适用场景总结

方案适用场景优点缺点
search_after实时数据顺序分页支持顺序深分页,减少内存消耗仅适用于顺序分页,无法随机跳转页数
scroll批量数据读取,非实时分页快照查询,适合大规模数据批量读取不适合实时查询,需释放游标以节省资源
分区过滤 + search_after实时数据大规模分页降低单次查询的数据量,减少深分页的扫描需根据业务分区,适用性受限
composite aggregation聚合查询深分页支持深分页的聚合查询,性能稳定仅适用于聚合查询场景

四、总结

Elasticsearch 的深分页问题主要是由 fromsize 的机制导致的。为了解决这一问题,可以使用 search_afterscroll、分区查询、composite aggregation 等方法进行优化。各方案的选择应根据具体业务需求和场景进行综合考量,合理利用 Elasticsearch 的分页机制,以实现性能与数据量的平衡。

0 0 投票数
Article Rating
订阅评论
提醒
guest
0 评论
最旧
最新 最多投票
内联反馈
查看所有评论
0
希望看到您的想法,请您发表评论x