Rails使用Searchkick如何实现Elasticsearch分组获取各分组文章ID
Elasticsearch按status分组返回对应文章ID实现方案
现有索引结构
索引中存储的文章文档格式如下:
{ "id": 1011, "title": "abcd", "author": "author1", "status": "published" }
需求说明
按status字段对所有文章分组,返回格式如下:
{ "published": [1011, 1012, ....], "draft": [2011], "deleted": [3011] }
之前使用Article.search('*',aggs: [:status], load: false).aggs的简化聚合写法,仅能返回各状态分组的文档计数,无法获取分组下的文章ID列表。
正确实现方式
普通terms聚合默认仅返回分桶计数,要获取桶内的文档字段,需要使用terms分桶聚合 + 嵌套子聚合的方式实现,以下是基于Ruby on Rails生态常用Elasticsearch客户端的实现代码:
- 自定义聚合查询DSL,通过top_hits子聚合拉取每个分桶下的文章ID:
# size:0 代表不返回原始命中文档,仅返回聚合结果,减少不必要的性能开销 search_result = Article.search( "*", body: { size: 0, query: { match_all: {} }, aggs: { group_by_status: { terms: { field: :status, size: 10 # 按实际status枚举值数量调整,确保覆盖所有状态 }, aggs: { article_ids: { top_hits: { size: 10000, # 按单状态下最大文章数调整,确保返回全量ID,默认top_hits仅返回10条 _source: [:id] # 仅返回id字段,降低数据传输量 } } } } } }, load: false )
- 遍历聚合结果,组装成目标格式:
grouped_article_ids = {} search_result.aggs["group_by_status"]["buckets"].each do |bucket| current_status = bucket["key"] current_ids = bucket["article_ids"]["hits"]["hits"].map { |hit| hit["_source"]["id"] } grouped_article_ids[current_status] = current_ids end
优化方案&注意事项
- 如果使用Elasticsearch 7.10及以上版本,且不需要获取文档其他字段,可以用terms子聚合替代top_hits,性能更优,子聚合部分替换为如下写法即可:
对应结果提取逻辑调整为:aggs: { article_ids: { terms: { field: :id, size: 10000 # 和top_hits的size规则一致,需大于单组最大文档数 } } }current_ids = bucket["article_ids"]["buckets"].map { |id_bucket| id_bucket["key"] } - 必须确保
status字段映射为keyword类型,否则会因为分词导致分桶结果不符合预期。 - 如果单状态下的文章量超过1万,不建议单次聚合拉取全量数据,可以使用composite聚合做分批游标拉取,避免ES内存压力过大。
内容的提问来源于stack exchange,提问作者Crazy Cat
相关产品推荐
相关产品推荐

