在使用 Elasticsearch 进行数据查询时,分页是一个常见的需求。不过,深度翻页会带来性能问题,下面就来详细聊聊如何优化 Elasticsearch 查询结果的分页,解决深度翻页的性能问题。

一、应用场景

在很多实际的业务场景中,我们都需要对 Elasticsearch 中的数据进行分页展示。比如电商平台的商品列表,用户可能会一页一页地浏览商品,当用户翻到后面的页面时,就涉及到深度翻页。又比如新闻网站,用户可能会不断地翻页查看更多的新闻。这些场景都需要对 Elasticsearch 查询结果进行分页处理,而深度翻页时性能问题就会凸显出来。

二、传统分页方式及问题

传统分页示例(Elasticsearch 技术栈)

{
    "query": {
        "match_all": {}
    },
    "from": 1000,  // 从第 1000 条记录开始
    "size": 10     // 每页显示 10 条记录
}

注释:这个查询表示从 Elasticsearch 中查询所有文档,从第 1000 条记录开始,每页显示 10 条记录。

传统的分页方式是通过 fromsize 参数来实现的。from 表示从第几条记录开始,size 表示每页显示的记录数。但是,当 from 的值很大时,就会出现性能问题。这是因为 Elasticsearch 在查询时,会先在每个分片上查询出 from + size 条记录,然后将这些记录合并、排序,最后再返回 size 条记录。随着 from 的增大,每个分片需要查询和排序的记录数也会增多,导致性能急剧下降。

技术优缺点

优点

  • 简单易懂,使用方便。只需要设置 fromsize 参数就可以实现分页。
  • 对于浅分页(from 值较小),性能影响不大。

缺点

  • 深度翻页性能差。当 from 值很大时,查询性能会显著下降。
  • 资源消耗大。每个分片需要查询和排序大量的记录,会占用大量的内存和 CPU 资源。

注意事项

  • 尽量避免深度翻页。如果用户需要查看大量数据,可以考虑提供搜索功能,让用户通过关键词筛选数据。
  • 对于 from 值较大的情况,要谨慎使用传统分页方式。

三、优化方案

方案一:Scroll API

原理

Scroll API 可以在 Elasticsearch 中创建一个快照,然后通过这个快照来获取数据。它避免了每次查询都需要重新排序的问题,从而提高了深度翻页的性能。

示例(Elasticsearch 技术栈)

// 初始化 Scroll API
{
    "query": {
        "match_all": {}
    },
    "size": 10,
    "scroll": "1m"  // 设置快照的有效期为 1 分钟
}

注释:这个查询表示查询所有文档,每页显示 10 条记录,并创建一个有效期为 1 分钟的快照。

// 使用 Scroll API 获取下一页数据
{
    "scroll": "1m",  // 延长快照的有效期为 1 分钟
    "scroll_id": "your_scroll_id"  // 上一次查询返回的 scroll_id
}

注释:这个查询表示使用上一次查询返回的 scroll_id 来获取下一页数据,并延长快照的有效期为 1 分钟。

优缺点

优点
  • 性能高。避免了每次查询都需要重新排序的问题,适合深度翻页。
  • 可以处理大量数据。可以通过不断滚动快照来获取大量数据。
缺点
  • 快照有效期有限。需要在有效期内使用 scroll_id 获取数据,否则快照会失效。
  • 不适合实时数据。由于快照是在创建时生成的,所以在快照有效期内,数据的更新不会反映在查询结果中。

注意事项

  • 及时释放快照。当不再需要使用快照时,要及时释放,避免占用过多的资源。
  • 注意快照的有效期。要根据实际情况设置合适的有效期。

方案二:Search After

原理

Search After 是一种基于排序的分页方式,它通过记录上一页的最后一条记录的排序值,来获取下一页的数据。这种方式避免了 from 参数的使用,从而提高了性能。

示例(Elasticsearch 技术栈)

// 第一页查询
{
    "query": {
        "match_all": {}
    },
    "size": 10,
    "sort": [
        {
            "timestamp": {
                "order": "desc"
            }
        }
    ]
}

注释:这个查询表示查询所有文档,每页显示 10 条记录,并按照 timestamp 字段降序排序。

// 后续页查询
{
    "query": {
        "match_all": {}
    },
    "size": 10,
    "sort": [
        {
            "timestamp": {
                "order": "desc"
            }
        }
    ],
    "search_after": [1630406400]  // 上一页最后一条记录的 timestamp 值
}

注释:这个查询表示查询下一页数据,每页显示 10 条记录,按照 timestamp 字段降序排序,并使用上一页最后一条记录的 timestamp 值作为 search_after 参数。

优缺点

优点
  • 性能高。避免了 from 参数的使用,适合深度翻页。
  • 实时性好。可以实时获取最新的数据。
缺点
  • 只能向后翻页。不支持向前翻页。
  • 需要排序字段。必须指定排序字段才能使用 Search After。

注意事项

  • 排序字段要具有唯一性。如果排序字段不唯一,可能会导致数据重复。
  • 要记录上一页的最后一条记录的排序值。在获取下一页数据时,需要使用这个值作为 search_after 参数。

四、总结

在 Elasticsearch 中进行分页查询时,传统的 fromsize 方式在深度翻页时会出现性能问题。为了解决这个问题,我们可以使用 Scroll API 或 Search After 等优化方案。Scroll API 适合处理大量数据的深度翻页,但快照有效期有限,不适合实时数据。Search After 性能高,实时性好,但只能向后翻页,且需要排序字段。在实际应用中,我们要根据具体的业务场景选择合适的分页方式。