elasticsearch 使用from+size深度分页性能问题&解决方案

问题分析

在 Elasticsearch 中,使用 from 和 size 参数进行分页查询时,深度分页(即请求非常高的页面号码)会导致性能问题,原因主要包括以下几点:

  1. 数据扫描量增加
  • 跳过大量文档:当你使用 from 参数来请求第 N 页的数据时,Elasticsearch 必须先扫描并跳过 (from - 1) * size 个文档才能开始收集实际需要返回的文档。例如,如果你请求第 100 页,每页 10 个结果,那么 Elasticsearch 需要跳过前 990 个文档。这在数据量大的情况下会显著增加查询时间和资源使用。
  1. 排序和聚合的开销
  • 排序复杂度:如果查询涉及排序(特别是基于非索引字段或需要复杂计算的排序),Elasticsearch 需要对所有跳过的文档进行排序操作,这增加了计算成本。

  • 聚合操作:如果查询同时涉及聚合操作,每个分片都可能需要处理所有数据来计算聚合结果,然后再进行合并,这进一步放大了性能开销。

  1. 内存消耗
  • 内存压力:在深度分页的情况下,Elasticsearch 需要在内存中保持大量文档的状态(特别是如果 size 设置得较大),这可能会导致内存不足(Out of Memory, OOM)错误,尤其是在处理大规模数据集时。
  1. 网络传输
  • 数据传输:虽然主要是内部网络,但在分布式环境下,协调节点需要从多个分片节点收集数据,这些数据传输量会随着分页深度的增加而增加。
  1. 协调节点的负担
  • 协调和合并:协调节点需要从所有相关的分片中收集结果,并将这些结果合并排序。这在深度分页时会变成一个相当重的任务,因为它需要处理更多数据。
  1. 默认限制
  • max_result_window:Elasticsearch 默认限制 from + size 不能超过 10,000。这是因为系统设计上不鼓励深度分页,超过这个限制会导致查询失败,除非你调整了这个设置,但这也只是治标不治本。

解决方案:

  1. Scroll API:

    使用 Scroll API 可以进行分批查询,避免深度分页的开销。Scroll API 在每个查询阶段存储状态,使得后续请求不必从头开始查询。适合后台批处理任务,如数据迁移,但不适用于实时搜索,因为它不反映索引的实时变化。
    *

  2. Search After:

    这是一种基于游标的分页方式,利用上一页的最后一个结果作为下一页查询的起点。每个文档需要一个全局唯一值(如 _id)来确保排序的一致性。适用于用户实时、高并发查询需求,因为它可以反映索引的实时变化。
    *

  3. 调整 max_result_window:

    • 虽然不是一种解决深度分页的长期策略,但通过增加 index.max_result_window 设置可以临时提高分页限制。不过,这仍然会增加资源消耗和潜在的OOM风险。
  4. Time-Based Pagination:

    • 如果数据有时间属性,可以根据时间范围进行分页,这样可以避免大量数据的扫描,提高查询效率。
  5. Shard-Based Pagination:

    • 针对分布式环境,通过利用分片来分页,可以在分布式环境下更好地管理查询负载。

注意事项:

1. Scroll API

Scroll API 适用于处理大量结果的场景,通过保持搜索上下文,连续获取下一批结果。

  • 优点:适合大批量数据处理,如数据导出、离线分析。
  • 缺点:不适合实时数据更新。
java 复制代码
import org.elasticsearch.action.search.*;
import org.elasticsearch.client.*;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.Scroll;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.SearchHit;

import java.io.IOException;

public class ElasticSearchScroll {
    private RestHighLevelClient client;

    public ElasticSearchScroll(RestHighLevelClient client) {
        this.client = client;
    }

    public void scrollSearch(String indexName) throws IOException {
        final Scroll scroll = new Scroll(TimeValue.timeValueMinutes(1L));
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder()
                .query(QueryBuilders.matchAllQuery())
                .size(1000); // Batch size
        SearchRequest searchRequest = new SearchRequest(indexName)
                .scroll(scroll)
                .source(searchSourceBuilder);

        SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
        String scrollId = searchResponse.getScrollId();
        SearchHit[] searchHits = searchResponse.getHits().getHits();

        while (searchHits != null && searchHits.length > 0) {
            for (SearchHit hit : searchHits) {
                System.out.println(hit.getSourceAsString());
            }

            SearchScrollRequest scrollRequest = new SearchScrollRequest(scrollId);
            scrollRequest.scroll(scroll);
            searchResponse = client.scroll(scrollRequest, RequestOptions.DEFAULT);
            scrollId = searchResponse.getScrollId();
            searchHits = searchResponse.getHits().getHits();
        }

        ClearScrollRequest clearScrollRequest = new ClearScrollRequest();
        clearScrollRequest.addScrollId(scrollId);
        client.clearScroll(clearScrollRequest, RequestOptions.DEFAULT);
    }
}

Search After 使用最后一个结果的排序值作为下一个查询的起点,避免了传统分页中的偏移操作。

  • 优点:适合实时数据分页,避免了大偏移量带来的性能问题。
  • 缺点:需要排序字段唯一(通常使用复合排序,如 _id)。
java 复制代码
import org.elasticsearch.action.search.*;
import org.elasticsearch.client.*;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.sort.SortOrder;

import java.io.IOException;

public class ElasticSearchSearchAfter {
    private RestHighLevelClient client;

    public ElasticSearchSearchAfter(RestHighLevelClient client) {
        this.client = client;
    }

    public void searchAfterPagination(String indexName, int pageSize) throws IOException {
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder()
                .query(QueryBuilders.matchAllQuery())
                .size(pageSize)
                .sort("timestamp", SortOrder.ASC)
                .sort("_id", SortOrder.ASC); // Ensure unique sort

        SearchRequest searchRequest = new SearchRequest(indexName)
                .source(searchSourceBuilder);

        SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
        SearchHit[] searchHits = searchResponse.getHits().getHits();

        while (searchHits.length > 0) {
            for (SearchHit hit : searchHits) {
                System.out.println(hit.getSourceAsString());
            }

            // Get the last sort value
            Object[] sortValues = searchHits[searchHits.length - 1].getSortValues();

            searchSourceBuilder.searchAfter(sortValues);
            searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
            searchHits = searchResponse.getHits().getHits();
        }
    }
}
相关推荐
IT毕设梦工厂1 小时前
大数据毕业设计选题推荐-基于大数据的客户购物订单数据分析与可视化系统-Hadoop-Spark-数据可视化-BigData
大数据·hadoop·数据分析·spark·毕业设计·源码·bigdata
java水泥工1 小时前
基于Echarts+HTML5可视化数据大屏展示-白茶大数据溯源平台V2
大数据·echarts·html5
广州腾科助你拿下华为认证3 小时前
华为考试:HCIE数通考试难度分析
大数据·华为
在未来等你5 小时前
Elasticsearch面试精讲 Day 17:查询性能调优实践
大数据·分布式·elasticsearch·搜索引擎·面试
大数据CLUB8 小时前
基于spark的澳洲光伏发电站选址预测
大数据·hadoop·分布式·数据分析·spark·数据开发
ratbag6720138 小时前
当环保遇上大数据:生态环境大数据技术专业的课程侧重哪些领域?
大数据
计算机编程小央姐10 小时前
跟上大数据时代步伐:食物营养数据可视化分析系统技术前沿解析
大数据·hadoop·信息可视化·spark·django·课程设计·食物
智数研析社11 小时前
9120 部 TMDb 高分电影数据集 | 7 列全维度指标 (评分 / 热度 / 剧情)+API 权威源 | 电影趋势分析 / 推荐系统 / NLP 建模用
大数据·人工智能·python·深度学习·数据分析·数据集·数据清洗
潘达斯奈基~11 小时前
《大数据之路1》笔记2:数据模型
大数据·笔记
寻星探路11 小时前
数据库造神计划第六天---增删改查(CRUD)(2)
java·大数据·数据库