如何通过date_histogram统计年度内每月首次出现的唯一用户
计算年度内每月首次出现的唯一用户(避免重复统计)
你要统计指定年份里每个月首次出现的唯一用户数——就是说用户只要在当年某个月份被统计过,后续月份就不能再计入。原来用date_histogram加cardinality的方法会重复统计跨月活跃的用户,而scripted_metric虽然结果对,但内存占得太高,大数据集下根本跑不动。下面给两个性能更优的解决方案:
方案1:用Elasticsearch Transform预处理数据(性能最优,首选)
先通过Transform预计算每个用户在目标年份的首次访问月份,之后直接基于预处理好的索引做聚合查询,这是大数据场景下最省心且高效的方式。
步骤1:创建Transform
这个Transform会给每个用户生成一条记录,包含用户ID和他当年第一次访问的月份:
PUT _transform/first_seen_month_transform { "source": { "index": ["你的源索引名"], "query": { "range": { "date": { "gte": "2024-01-01", "lte": "2024-12-31" } } } }, "dest": { "index": "user_first_seen_2024" }, "pivot": { "group_by": { "user": { "terms": { "field": "user" } } }, "aggregations": { "first_seen_month": { "min": { "field": "date", "format": "yyyy-MM" } } } }, "sync": { "time": { "field": "date", "delay": "1h" } } }
步骤2:启动Transform
POST _transform/first_seen_month_transform/_start
步骤3:查询预处理后的索引
直接按first_seen_month聚合统计数量,结果完全符合你的需求,而且速度极快:
GET user_first_seen_2024/_search { "size": 0, "aggs": { "monthly_counts": { "terms": { "field": "first_seen_month", "order": { "_key": "asc" } } } } }
返回的结果格式大概是这样,和你期望的结构很接近,稍作整理就能用:
"aggregations": { "monthly_counts": { "buckets": [ { "key": "2024-01", "doc_count": 4 }, { "key": "2024-02", "doc_count": 4 }, { "key": "2024-03", "doc_count": 3 }, { "key": "2024-04", "doc_count": 2 }, { "key": "2024-05", "doc_count": 1 } ] } }
方案2:单查询直接实现(无需预处理,适合小数据集)
如果不想做预处理,也可以用嵌套聚合直接查,但这个方法只适合用户量不大的场景——因为要把所有用户都拉出来统计:
GET 你的源索引名/_search { "size": 0, "query": { "range": { "date": { "gte": "2024-01-01", "lte": "2024-12-31" } } }, "aggs": { "users": { "terms": { "field": "user", "size": 10000 // 这里要设足够大,能覆盖所有用户,不然会丢数据 }, "aggs": { "first_seen": { "min": { "field": "date", "format": "yyyy-MM" } } } }, "monthly_new_users": { "terms": { "script": "doc['first_seen'].value", "order": { "_key": "asc" } } } } }
为啥原来的方案不行?
你之前用的date_histogram加cardinality,统计的是每个月的独立活跃用户——不管用户之前有没有出现过,只要当月有活动就算进去,自然不符合“只统计首次出现”的要求。而scripted_metric要在内存里存所有已经出现过的用户,数据量一大内存就爆,性能肯定拉胯。
内容的提问来源于stack exchange,提问作者Sheel
相关产品推荐
相关产品推荐

