Elasticsearch terms聚合如何获取全量食材出现次数统计结果
出错原因
你遇到的不是数据写入或者索引结构的问题,核心原因是Elasticsearch的terms聚合存在默认返回条数限制,没有配置参数的情况下只会返回按出现次数降序排列的前10个聚合桶。
你返回结果里的两个字段已经能佐证这个逻辑:
sum_other_doc_count: 4709:代表没有进入返回列表的其余食材对应的总出现次数doc_count_error_upper_bound: 56:代表分布式聚合统计带来的计数误差上限
你的索引中ingredient字段设置为keyword类型存储字符串数组的写法是正确的,terms聚合会自动对数组内的每个元素独立统计出现次数,不需要调整mapping结构。
仅返回部分结果的原理
这个默认限制是Elasticsearch做的性能权衡设计,执行逻辑如下:
- terms聚合是分布式执行的:查询请求发到协调节点后,会转发给所有相关分片,每个分片独立计算本地分片内的食材聚合结果,生成本地的排序桶列表,再上报给协调节点做全局合并排序。
- 默认参数下,每个分片只会给协调节点上报本地排名前10的聚合桶,协调节点把所有分片的上报结果合并后,也只返回全局排名前10的结果。这种设计可以避免当聚合字段基数极大(比如存在上百万个唯一值)时,大量数据在分片和协调节点之间传输、占用过多内存导致查询延迟飙升甚至节点故障,覆盖绝大多数通用聚合场景的需求。
- 因为分片只上报部分桶,协调节点合并时可能漏掉部分在单分片排名靠后、但全局累加后排名靠前的桶,所以结果中会附带
doc_count_error_upper_bound字段提示最大可能的计数误差。
返回全量食材计数的实现方法
只需要在terms聚合中增加size参数,将值设置为大于你索引中实际食材总种类的数值即可。结合你的数据集规模(不到1000份食谱,食材总种类不会超过1万),可以直接用如下查询:
{ "size": 0, "aggs": { "ingredients": { "terms": { "field": "ingredient", "size": 10000, "shard_size": 10000 } } } }
参数说明:
size: 10000:指定协调节点最终返回的聚合桶最大数量,设置为10000完全可以覆盖你所有的食材种类。shard_size: 10000:指定每个分片上报给协调节点的聚合桶数量,设置和size一致的值后,可以彻底消除分布式聚合带来的计数误差,返回结果中doc_count_error_upper_bound会变为0,统计结果100%准确。
因为你的总数据量很小,就算把size设置为10000,也不会对查询性能、节点内存造成明显影响,可以放心使用。注意不要在基数极大的字段(比如千万级唯一值的用户ID、订单号字段)上设置过大的size,避免触发内存溢出问题,你的食材统计场景不存在这个风险。
内容的提问来源于stack exchange,提问作者tbmsilva
相关产品推荐
相关产品推荐

