自学内容网 自学内容网

ES使用笔记,聚合分组后再分页,探索性能优化问题

之前分享过一篇文档,也是关于聚合分组后再分页的具体实现,当时只想着怎么实现,没有去主要探索ES性能优化的问题,
这篇我会换一种方式,重新实现这个聚合分组后再分页的操作,并且指出能优化性能点,可能我们再使用的时候,并没有注意过的点,希望对你有帮助!大佬的话,请忽略!
上源码

public SearchResultVo searchSupplier(SearchPageVo searchPageVo) {
    // 创建搜索结果对象
    SearchResultVo searchResultVo = new SearchResultVo();
    // 获取基础的搜索请求对象
    SearchRequest searchRequest = CloudBaseQueryBuilder.getBaseSubOrderIndexRequest();
    // 创建搜索源构建器
    SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
    // 创建机会页面对象并设置目录ID
    OpportunityPageVo vo = new OpportunityPageVo();
    vo.setCatalogIds(searchPageVo.getCatalogId());
    // 根据搜索类型设置供应商名称或单位名称
    if (PlatformConstant.COMMON_TYPE_YES.equals(searchPageVo.getType())) {
        vo.setSupplierName(searchPageVo.getName());
    } else {
        vo.setUnitName(searchPageVo.getName());
    }
    // 获取基础的条件查询
    BoolQueryBuilder boolQuery = CloudBaseQueryBuilder.getBaseOpportunityBoolQuery(vo);
    // 设置分组聚合
    int termsSize = (searchPageVo.getPageNo() - 1) * searchPageVo.getPageSize() + searchPageVo.getPageSize();
    TermsAggregationBuilder termsAggregationBuilder = AggregationBuilders
            .terms("aggregation")
            .field(PlatformConstant.COMMON_TYPE_YES.equals(searchPageVo.getType()) ? "supplierName.keyword" : "unitName.keyword")
            .size(termsSize)
            .shardSize(termsSize * 2 + 10)
            .subAggregation(AggregationBuilders
                    .filters("orderTypeFilters",
                            new FiltersAggregator.KeyedFilter("Agreement", QueryBuilders.termQuery("orderType", 3)),
                            new FiltersAggregator.KeyedFilter("Estore", QueryBuilders.termQuery("orderType", 1))
                    )
                    .subAggregation(AggregationBuilders.sum("totalPriceSum").field("totalPrice")) // 聚合总金额
            )
            .subAggregation(AggregationBuilders
                    .cardinality("orderCount")
                    .field("id.keyword")
            );
    // 设置总唯一值聚合
    CardinalityAggregationBuilder totel = AggregationBuilders
            .cardinality("totel")
            .field(PlatformConstant.COMMON_TYPE_YES.equals(searchPageVo.getType()) ? "supplierName.keyword" : "unitName.keyword")
            // 精确度越高 内存消耗越大
            .precisionThreshold(40000);

    // 设置过滤器聚合
    FilterAggregationBuilder filter = AggregationBuilders.filter("bool_filter", boolQuery);
    List<Object> objectList = new ArrayList<>();
    objectList.add("orderCount");
    objectList.add(SortOrder.DESC);
    // 设置桶排序聚合
    termsAggregationBuilder.subAggregation(new BucketSortPipelineAggregationBuilder("bucket_sort",
            Arrays.asList(
                    new FieldSortBuilder((String) objectList.get(0)).order((SortOrder) objectList.get(1))))
            .from((searchPageVo.getPageNo() - 1) * searchPageVo.getPageSize())
            .size(searchPageVo.getPageSize()));
    // 将分组聚合和总唯一值聚合添加到过滤器聚合中
    filter.subAggregation(termsAggregationBuilder);
    filter.subAggregation(totel);
    // 将过滤器聚合添加到搜索源构建器中
    sourceBuilder.aggregation(filter);
    // 设置搜索请求的源
    searchRequest.source(sourceBuilder);
    // 记录搜索请求日志
    log.info("searchRequest:" + sourceBuilder.toString());
    try {
        // 执行搜索请求并获取响应
        SearchResponse searchResponse = elasticsearchClient.search(searchRequest, RequestOptions.DEFAULT);
        // 获取过滤器聚合结果
        Filter boolFilter = searchResponse.getAggregations().get("bool_filter");
        // 获取总唯一值聚合结果
        Cardinality cardinality = boolFilter.getAggregations().get("totel");
        // 获取分组聚合结果
        Terms unitNameAggregation = boolFilter.getAggregations().get("aggregation");
        List<? extends Terms.Bucket> buckets = unitNameAggregation.getBuckets();

        List<SearchListVo> searchList = new ArrayList<>();
        // 遍历每个分组聚合桶
        for (Terms.Bucket bucket : buckets) {
            SearchListVo searchListVo = new SearchListVo();
            // 设置供应商名称或单位名称
            if (PlatformConstant.COMMON_TYPE_YES.equals(searchPageVo.getType())) {
                searchListVo.setSupplierName(bucket.getKeyAsString());
            } else {
                searchListVo.setUnitName(bucket.getKeyAsString());
            }
            // 获取订单类型过滤器聚合结果
            Filters filters = bucket.getAggregations().get("orderTypeFilters");
            // 设置电商订单金额
            searchListVo.setEstoreAmount(BigDecimal.valueOf(((Sum) filters.getBucketByKey("Estore").getAggregations().get("totalPriceSum")).getValue()));
            // 设置协议订单金额
            searchListVo.setAgreementAmount(BigDecimal.valueOf(((Sum) filters.getBucketByKey("Agreement").getAggregations().get("totalPriceSum")).getValue()));
            // 获取订单数量聚合结果
            Cardinality orderCount = bucket.getAggregations().get("orderCount");
            searchListVo.setSum((int) orderCount.getValue());

            searchList.add(searchListVo);
        }
        // 设置搜索结果的总数和列表
        searchResultVo.setTotal(Long.valueOf(cardinality.getValue()).intValue());
        searchResultVo.setList(searchList);
    } catch (IOException e) {
        // 记录搜索错误日志
        log.error("Error searching supplier", e);
    }

    return searchResultVo;
}

代码中的注释,方便我的代码逻辑,其中分页我还是用到了BucketSortPipelineAggregationBuilder这个类
BucketSortPipelineAggregationBuilder 是 Elasticsearch 中用于对聚合桶进行排序和分页的类。下面是对该类的作用、意义、使用场景、优点以及对性能的影响的详细解释:

  1. 作用和意义 排序:允许你对聚合结果中的桶进行排序。例如,可以按某个聚合值(如订单数量、总金额等)对供应商或单位进行排序。 分页:通过设置 from 和 size 参数,可以实现对聚合结果的分页,从而支持分页查询。 为什么使用这个类
  2. 复杂排序需求:当需要对聚合结果进行复杂的排序(例如,按多个字段排序)时,BucketSortPipelineAggregationBuilder
    提供了灵活的排序选项。 分页支持:在处理大量聚合结果时,分页是非常重要的,可以减少每次查询返回的数据量,提高查询效率和用户体验。
  3. 优点 灵活性:支持多种排序方式,包括按字段排序、按脚本排序等。 分页能力:内置分页功能,方便实现分页查询。 性能优化:通过减少每次查询返回的数据量,可以显著提高查询性能,尤其是在处理大量数据时。
    内存使用优化:通过分页和排序,可以减少内存占用,避免一次性加载大量数据导致的内存问题。
  4. 对性能的影响 减少数据传输:通过分页,每次查询只返回所需的数据量,减少了网络传输的数据量。 减少内存占用:避免一次性加载大量数据到内存中,减少了内存压力。
    提高查询速度:通过排序和分页,可以更快地定位和返回所需的数据,提高查询速度。

再构建聚合时,使用了TermsAggregationBuilder类,其中两个参数size和shardSize,这两个参数可以优化性能,如果不设置的话,ES默认size=10,shardSize也是10,在 Elasticsearch 的 terms 聚合中,size 和 shard_size 参数用于控制聚合结果的数量和分片级别的聚合结果数量
两个参数的区别:

  1. size: 作用: 控制最终返回的分组数量。 示例: size: 20 表示最终返回最多 20 个供应商分组。

  2. shard_size: 作用: 控制每个分片返回的分组数量。 示例: shard_size: 10 表示每个分片最多返回 10
    个供应商分组。

  3. size 和 shard_size 的关系
    shard_size:
    每个分片独立地对数据进行聚合,并返回最多 shard_size 个分组。
    例如,如果有 5 个分片,每个分片返回 10 个分组,那么协调节点将收到 50 个分组。
    size:
    协调节点从所有分片收集到的分组中,选择前 size 个分组。
    例如,如果 size: 20,协调节点将从 50 个分组中选择前 20 个分组。

  4. 性能优化:
    减少网络传输: 通过设置较小的 shard_size,可以减少每个分片返回的数据量,从而减少网络传输开销。
    提高效率: 协调节点只需处理较少的中间结果,从而提高整体查询效率。
    准确性:
    避免遗漏: 通过设置较大的 shard_size,可以确保每个分片返回足够的分组,从而避免在协调节点合并时遗漏重要的分组。
    精确排序: 确保最终返回的分组是全局排序后的结果,而不是每个分片排序后的结果。

  5. 示例
    假设有一个索引包含 100 个供应商,每个分片包含 20 个文档。配置如下:
    size: 20: 最终返回 20 个供应商分组。
    shard_size: 10: 每个分片返回 10 个供应商分组。
    分片聚合
    分片 1: 返回供应商 A, B, C, D, E, F, G, H, I, J。
    分片 2: 返回供应商 K, L, M, N, O, P, Q, R, S, T。
    分片 3: 返回供应商 U, V, W, X, Y, Z, A1, B1, C1, D1。
    分片 4: 返回供应商 E1, F1, G1, H1, I1, J1, K1, L1, M1, N1。
    分片 5: 返回供应商 O1, P1, Q1, R1, S1, T1, U1, V1, W1, X1。
    协调节点聚合
    合并分组: 协调节点将所有分组合并,得到 50 个供应商分组。
    排序: 协调节点按文档数量降序排序,如果数量相同则按供应商名称升序排序。
    选择前 20 个分组: 最终返回前 20 个供应商分组。

  6. 冲突问题
    冲突: 由于 shard_size 控制每个分片返回的分组数量,如果 shard_size 设置得太小,可能会导致某些重要的分组被遗漏,从而影响最终结果的准确性。
    避免冲突: 通过合理设置 shard_size,确保每个分片返回足够的分组,从而避免在协调节点合并时遗漏重要的分组。

  7. 总结
    shard_size 和 size 的关系是:shard_size 控制每个分片返回的分组数量,size 控制最终返回的分组数量。
    性能优化: 通过设置较小的 shard_size,可以减少网络传输开销,提高查询效率。
    准确性: 通过设置较大的 shard_size,可以确保每个分片返回足够的分组,从而避免遗漏重要的分组,确保最终结果的准确性。所以shard_size的大小要我们的数据量,业务需求去调整,没有绝对的正确,只有相对的平衡
    还有个计算分组的总条数,我使用的是计算唯一的类:CardinalityAggregationBuilder,其中precisionThreshold就是调整的阙值,这个参数决定了Elasticsearch在计算基数时使用的内存大小和精度之间的权衡。较高的阈值会提高精度但增加内存消耗,

那么在ES中还有哪些聚合方式:

在Elasticsearch中,除了 TermsAggregationBuilder,还有其他几种常用的聚合类。以下是几种常见的聚合类及其特点、优缺点和对性能的影响:

  1. Terms Aggregation

    作用:用于按字段值进行分组统计。
    优点:
    灵活性高,支持多种子聚合。
    支持排序、过滤等操作。
    缺点:
    对于大基数(大量唯一值)字段,性能较差,内存消耗较大。
    需要设置合理的 shardSize 和 precisionThreshold 来平衡精度和性能。
    性能影响:随着唯一值数量增加,性能会显著下降。

  2. Histogram Aggregation

    作用:用于数值字段的区间分组统计。
    优点:
    适合数值型数据的区间分析。
    计算简单,性能较好。
    缺点:
    不适用于非数值字段。
    区间划分需要预先设定。
    性能影响:性能较好,但依赖于区间的合理设置。

  3. Date Histogram Aggregation
    作用:用于日期字段的时间区间分组统计。
    优点:
    专门针对日期字段优化。
    支持灵活的时间间隔(如天、月、年)。
    缺点:
    仅适用于日期字段。
    性能影响:性能较好,特别是对于时间序列数据。

  4. Range Aggregation
    作用:用于数值或日期字段的范围分组统计。
    优点:
    支持自定义范围。
    适合分析特定范围内的数据分布。
    缺点:
    需要手动定义范围边界。
    性能影响:性能较好,取决于范围的数量和复杂度。

  5. Composite Aggregation
    作用:用于多字段组合分组统计,支持深度分页。
    优点:
    支持多字段组合分组。
    支持深度分页,避免一次性加载大量数据。
    缺点:
    复杂度较高,配置较为繁琐。
    性能影响:性能较好,特别适合大数据集的分页查询。

  6. Significant Terms Aggregation
    作用:用于发现显著不同的项,常用于异常检测。
    优点:
    适合发现异常或显著变化的数据。
    自动计算显著性。
    缺点:
    计算复杂,性能较低。
    性能影响:性能较差,适合小规模数据集或特定场景。

  7. Cardinality Aggregation
    作用:用于计算唯一值的数量。
    优点:
    简单易用,适合唯一值统计。
    缺点:
    对于大基数字段,性能较差,内存消耗大。
    性能影响:性能取决于 precisionThreshold 的设置。

  8. 聚合类的选择建议
    如果需要按离散值分组:使用 Terms Aggregation 或 Composite Aggregation。Composite Aggregation 更适合大数据集的分页查询。
    如果需要按数值或日期区间分组:使用 Histogram Aggregation 或 Date Histogram Aggregation。
    如果需要按范围分组:使用 Range Aggregation。
    如果需要发现显著变化的数据:使用 Significant Terms Aggregation。
    如果需要计算唯一值数量:使用 Cardinality Aggregation,并根据实际需求调整 precisionThreshold。


原文地址:https://blog.csdn.net/CuiGongZi/article/details/145258674

免责声明:本站文章内容转载自网络资源,如侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!