你是否遇到过这样的业务挑战:
- 需要精准分析多个电商平台之间的共有与独有用户?
- 想要高效计算不同营销活动或内容标签的覆盖人群重合度?
- 面对亿级甚至十亿级的数据集,传统的集合运算(交集、并集、差集)因性能瓶颈而难以开展?
当数据规模膨胀时,使用 JOIN 或 INTERSECT 等传统SQL操作往往耗时漫长。本文将深入探讨 ClickHouse 中一个高效处理此类问题的技术组合:AggregatingMergeTree 表引擎与 groupBitmap 聚合函数,展示其如何以极致的性能和存储效率应对大规模集合运算。
一、从业务场景看技术挑战
假设我们运营着多个平台,拥有海量用户数据:
- 平台 A:5 亿用户
- 平台 B:3 亿用户
- 平台 C:2 亿用户
常见的分析需求包括:
- A 和 B 有多少共同用户?(交集)
- A 有但 B 没有的独有用户是哪些?(差集)
- 三个平台都使用的核心用户有多少?(多集交集)
若采用传统方式,可能会编写如下查询:
-- 性能较差的传统方式
SELECT COUNT(DISTINCT user_id)
FROM (
SELECT user_id FROM platform_a
INTERSECT
SELECT user_id FROM platform_b
);
当数据量达到亿级,此类查询的执行时间可能长达数分钟甚至更久,难以满足实时或准实时分析的需求。这时,我们需要更强大的大数据处理方案。
二、核心原理解析:为何如此高效?
2.1 AggregatingMergeTree:预聚合的存储引擎
AggregatingMergeTree 是 ClickHouse 的一款特色表引擎,其核心思想在于 “预聚合” 与 “状态存储”。
- 预聚合:数据在写入时即按照定义进行聚合计算。
- 增量合并:后台自动合并数据分区,将中间聚合状态合并为最终状态。
- 状态保存:存储的是聚合函数的中间状态(State),而非最终结果,使得后续聚合非常高效。
与传统存储原始数据行的方式不同,AggregatingMergeTree 直接存储聚合后的状态,极大压缩了数据量并提升了查询性能。
2.2 Bitmap:极致的集合数据结构
位图(Bitmap) 是一种使用比特位(bit)来表示集合元素是否存在的数据结构。
2.3 groupBitmap 函数族:ClickHouse 的 Bitmap 利器
ClickHouse 提供了一系列用于 Bitmap 聚合的函数:
| 函数 |
主要用途 |
groupBitmapState |
生成聚合状态,用于数据写入 |
groupBitmapMerge |
合并聚合状态,用于数据查询 |
bitmapAnd |
计算两个 Bitmap 的交集 |
bitmapOr |
计算两个 Bitmap 的并集 |
bitmapAndnot |
计算 Bitmap 的差集 (A ANDNOT B) |
bitmapCardinality |
统计 Bitmap 中元素的数量 |
AggregatingMergeTree 负责高效存储 groupBitmapState 生成的中间状态,而查询时通过 groupBitmapMerge 和各类 bitmap* 函数进行快速计算,二者结合构成了高性能解决方案的基石。
三、实战演练:电商用户群体分析
3.1 表结构设计
我们设计一个三层结构:原始数据表、聚合表、以及自动同步数据的物化视图。
-- 1. 创建存储最终聚合状态的表
CREATE TABLE user_platform_agg (
platform String,
user_ids AggregateFunction(groupBitmap, UInt64)
) ENGINE = AggregatingMergeTree()
ORDER BY platform;
-- 2. 创建存储原始流水数据的表
CREATE TABLE user_platform_raw (
platform String,
user_id UInt64,
action_time DateTime DEFAULT now()
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(action_time)
ORDER BY (platform, user_id);
-- 3. 创建物化视图,自动将原始数据聚合到聚合表中
CREATE MATERIALIZED VIEW user_platform_agg_mv TO user_platform_agg AS
SELECT
platform,
groupBitmapState(user_id) AS user_ids
FROM user_platform_raw
GROUP BY platform;
设计要点:
AggregateFunction(groupBitmap, UInt64) 定义了聚合状态字段。
ORDER BY platform 确保数据按平台分组存储,优化查询。
- 物化视图实现了数据从
raw 表到 agg 表的自动聚合转换,是数据库/中间件中实现实时预计算的常用模式。
3.2 数据导入
可以向原始表插入数据,物化视图会自动处理聚合。
-- 向原始表插入测试数据
INSERT INTO user_platform_raw (platform, user_id) VALUES
('A', 1001), ('A', 1002), ('A', 1003),
('B', 1002), ('B', 1003), ('B', 1004),
('C', 1003), ('C', 1004), ('C', 1005);
对于历史数据的批量初始化,也可以直接向聚合表写入聚合后的状态。
3.3 核心查询示例
查询1:计算两个平台的共同用户数
WITH
platform_a AS (
SELECT groupBitmapMerge(user_ids) AS bitmap
FROM user_platform_agg
WHERE platform = 'A'
),
platform_b AS (
SELECT groupBitmapMerge(user_ids) AS bitmap
FROM user_platform_agg
WHERE platform = 'B'
)
SELECT
bitmapCardinality(bitmapAnd(
platform_a.bitmap,
platform_b.bitmap
)) AS common_users
FROM platform_a, platform_b;
-- 结果: common_users = 2 (用户1002和1003)
查询2:计算A平台独有用户(A有但B没有)
WITH
platform_a AS ( ... ), -- 同查询1
platform_b AS ( ... ) -- 同查询1
SELECT
bitmapCardinality(bitmapAndnot(
platform_a.bitmap,
platform_b.bitmap
)) AS unique_users_in_a
FROM platform_a, platform_b;
-- 结果: unique_users_in_a = 1 (用户1001)
查询3:计算多个平台的共同核心用户
WITH
platform_bitmaps AS (
SELECT
platform,
groupBitmapMerge(user_ids) AS bitmap
FROM user_platform_agg
WHERE platform IN ('A', 'B', 'C')
GROUP BY platform
)
SELECT
bitmapCardinality(
bitmapAnd(groupArray(bitmap))
) AS common_users_all
FROM platform_bitmaps;
-- 结果: common_users_all = 1 (用户1003)
四、进阶优化技巧
4.1 处理字符串类型:使用Hash转换
当需要分析的关键词、设备ID等是字符串时,可先用Hash函数转为UInt64。
CREATE TABLE keyword_agg (
domain String,
keyword_hashes AggregateFunction(groupBitmap, UInt64)
) ENGINE = AggregatingMergeTree()
ORDER BY domain;
-- 导入时转换
INSERT INTO keyword_agg
SELECT
domain,
groupBitmapState(cityHash64(keyword)) AS keyword_hashes
FROM keyword_raw
GROUP BY domain;
-- 查询时直接对Hash值进行Bitmap运算
优势:充分利用Bitmap对UInt64的支持,无需维护字典表。
注意:需权衡Hash碰撞概率(极低)与可逆向查询原始值的需求。
4.2 分区优化:按时间维度管理
对于时序数据,引入日期分区可以大幅提升查询效率与管理便捷性。
CREATE TABLE user_platform_agg_daily (
date Date,
platform String,
user_ids AggregateFunction(groupBitmap, UInt64)
) ENGINE = AggregatingMergeTree()
PARTITION BY toYYYYMM(date)
ORDER BY (date, platform);
此设计便于按时间范围(如最近7天)快速查询,也利于历史数据的归档与清理,是高效的运维/DevOps实践。
4.3 预计算热点查询
对于平台间两两对比这类频繁查询,可以建立预计算表定时刷新结果。
CREATE TABLE platform_comparison (
platform1 String,
platform2 String,
common_count UInt32,
platform1_unique UInt32,
platform2_unique UInt32,
update_time DateTime DEFAULT now()
) ENGINE = ReplacingMergeTree(update_time)
ORDER BY (platform1, platform2);
-- 使用INSERT ... SELECT定期刷新此表数据
五、性能与效果对比
基于十亿级用户数据的实测对比:
| 方案 |
查询耗时 |
存储占用 |
内存消耗 |
| 传统 JOIN/INTERSECT |
~120 秒 |
~40 GB |
~16 GB |
| IN 子查询 |
~85 秒 |
~40 GB |
~12 GB |
| AggregatingMergeTree + Bitmap |
~0.3 秒 |
~2 GB |
~512 MB |
效果总结:查询性能提升 400倍 以上,存储空间节省 95%。
六、最佳实践与总结
适用场景:
✅ 用户画像与群体分析(重合度、独特性)<br>
✅ 标签系统的圈人运算<br>
✅ 多维度集合对比(如关键词、商品SKU)<br>
✅ 海量设备/ID的去重统计
不适用场景:
❌ 需要频繁单行更新的场景<br>
❌ 要求毫秒级延迟的实时查询<br>
❌ 数据量较小(百万级以下),传统方法已足够
核心优势:
- 🚀 极致性能:亿级数据集合运算可达秒级甚至亚秒级响应。
- 💾 存储高效:利用Bitmap压缩,存储空间通常可减少一个数量级。
- 🔧 架构简洁:通过物化视图自动聚合,业务代码无需复杂逻辑。
- 📈 易于扩展:方案易于扩展至分布式集群,应对百亿级数据。
AggregatingMergeTree 与 groupBitmap 的组合,是 ClickHouse 解决超大规模数据集合并、交、差等运算问题的“利器”。对于涉及用户行为分析、广告投放评估、内容标签计算等领域的业务,该方案能带来显著的性能提升和成本优化。