如何加速从 Elasticsearch 多索引中获取唯一值?

通过单次跨索引聚合(terms on `_index` + 子聚合 on `provider.keyword`),替代逐索引循环查询,可显著提升多索引去重查询性能。

在 Elasticsearch 中,当需要从上百个索引中提取某个字段(如 provider.keyword)的所有唯一值时,若采用“遍历每个索引 → 单独执行 terms 聚合 → 合并结果”的方式(即原始代码中的 for 循环),不仅会产生大量 HTTP 请求和网络开销,还会因重复初始化搜索上下文、分片协调及结果归并而严重拖慢响应速度。

更优解:使用跨索引聚合 + 嵌套聚合(Multi-level Aggregation)

Elasticsearch 支持对多个索引(甚至通配符或 _all)一次性执行聚合操作。关键优化点如下:

  • ✅ 使用 indices("_all") 或通配符索引名(如 "logs-*")发起单次请求,避免 N 次 round-trip;
  • ✅ 设置 .size(0) 禁用 hits 返回,仅关注聚合结果,减少序列化与传输开销;
  • ✅ 构建两级 terms 聚合:外层按 _index 分桶(便于后续区分来源),内层按 provider.keyword 分桶并自动去重;
  • ✅ 为两级聚合显式设置足够大的 .size()(如 100),防止高频值被截断(注意:ES 默认仅返回前

    10 个 terms,需主动扩容)。

示例优化代码(Java High-Level REST Client):

SearchRequest searchRequest = new SearchRequest();
searchRequest.indices("_all"); // 或更安全的 getIndicesPattern(),如 "myapp-202*-*" 

SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(QueryBuilders.matchAllQuery()).size(0); // 关键:不返回文档内容

String aggregationIndexName = "by_index";
String aggregationProviderName = "by_provider";

searchSourceBuilder.aggregation(
    AggregationBuilders.terms(aggregationIndexName)
        .field("_index")
        .size(100) // 确保覆盖全部索引(索引数 ≤ 100)
        .subAggregation(
            AggregationBuilders.terms(aggregationProviderName)
                .field("provider.keyword")
                .size(1000) // 根据业务预估 provider 去重后数量,建议 ≥ 实际唯一值量级
        )
);

searchRequest.source(searchSourceBuilder);

SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);

解析聚合结果(提取全部唯一 provider 值):

Terms indicesAgg = response.getAggregations().get(aggregationIndexName);
Set allUniqueProviders = new HashSet<>();

for (Terms.Bucket indexBucket : indicesAgg.getBuckets()) {
    Terms providerAgg = indexBucket.getAggregations().get(aggregationProviderName);
    for (Terms.Bucket providerBucket : providerAgg.getBuckets()) {
        allUniqueProviders.add(providerBucket.getKeyAsString());
    }
}
// allUniqueProviders 即为全量去重后的 provider 列表

⚠️ 注意事项:

  • _all 在 ES 7.x+ 已弃用,生产环境推荐显式传入索引列表(如 client.indices().getAlias(...) 动态获取)或使用索引别名;
  • 若 provider.keyword 字段存在大量唯一值(>10k),需考虑启用 collect_mode: breadth_first 或调整 execution_hint,但通常 size=1000 已满足多数场景;
  • 确保 provider.keyword 字段已正确定义为 keyword 类型(而非 text),否则无法用于精确聚合;
  • 高并发下建议添加超时控制(searchRequest.requestOptions().withTimeout(...))并捕获 ElasticsearchStatusException。

综上,将 N 次独立聚合收敛为 1 次嵌套聚合,是提升多索引唯一值提取性能最直接、最有效的方式——既降低集群负载,又大幅缩短端到端延迟。