原生 BitSet 不适用于十亿级用户画像标签过滤,因其内存占用大(125 MB/标签)、不支持稀疏优化、无高效交并补运算、不可分片与序列化;RoaringBitmap 通过容器分层、并行计算、紧凑序列化和引擎集成解决上述问题。
BitSet 本身不适用于十亿级用户画像的标签过滤——它在单机内存中无法承载 10⁹ 量级的 bit(需约 125 MB / 标签,且无法压缩、无交并补优化),实际生产系统早已弃用原生
,转而采用 RoaringBitmap 或类似结构。
为什么原生
在十亿场景下直接失效
Java 的
是纯内存、非压缩、稀疏效率差的数据结构:
10 亿用户对应
长度为 10⁹,底层用 long[] 存储,需约 125 MB 内存 / 标签;10 万标签即超 12 TB 内存,完全不可行
不支持稀疏位集优化:若一个标签只覆盖 500 万用户(0.5% 稀疏度),
仍分配全部空间,而 RoaringBitmap 此时仅占 ~12 MB
无内置交/并/差集的向量化计算能力,
/
是逐 word 循环,百亿 bit 操作耗时秒级起跳
无法分片、无法序列化传输、不兼容分布式查询(如 Greenplum、ClickHouse 插件)
替代
的关键改造点
真正落地十亿级标签筛选的,是
—— 它不是“BitSet 的升级版”,而是从设计上就面向海量稀疏布尔集合的压缩位图结构:
自动按数据密度切分:对连续段用
(小范围高效),对密集段用
(64KB 固定大小),对超大范围用
(游程编码)
所有集合运算(
、
、
、
)均基于 container 粒度并行,实测 10 亿用户 × 10 万标签交集可在毫秒内完成
支持序列化为紧凑字节数组,可直接落盘或通过网络传输;Greenplum、ClickHouse 均有官方或社区
扩展支持
与列式引擎天然契合:例如 ClickHouse 的
可将用户 ID 流实时聚合成 bitmap,后续
即标签圈选
在 Greenplum + RoaringBitmap 中实现标签圈选的最小可行路径
这不是“写个 Java 工具类”能解决的问题,必须嵌入 OLAP 引擎执行层。以 Greenplum 为例(参考 2019 年 digoal 方案演进版):
建表时用
类型存储每个标签的用户集合:
导入数据前先按用户 ID 分桶(如 mod 1000),再批量调用
构造 bitmap,避免单次构造超时
圈选逻辑直接 SQL 化:
务必开启分区:按
或时间范围分区,否则全表扫描 bitmap 列会退化为磁盘 IO 密集型操作
避免在应用层做多次
拼接——应尽量收拢为一条 SQL,让 GP 的 MPP 引擎并行执行容器级运算
容易被忽略的三个硬伤点
即便用了
,以下三点仍会导致查询从毫秒退化到秒级甚至超时:
用户 ID 未归一为 uint32:RoaringBitmap 对 >2³² 的 key 支持极差,10 亿用户必须映射到
范围内,不能直接用微信 OpenID 或手机号哈希值(可能超界)
未预热 bitmap 缓存:GP 中 roaringbitmap 运算虽快,但首次加载大 bitmap 仍需解压+构建 container 索引,冷查询延迟抖动明显;建议用
预触一次热点标签
误把 bitmap 当明细表用:有人试图用
拉出全部用户 ID 再 Join 业务表——这等于放弃位图优势;正确做法是用
或子查询关联,让引擎在 bitmap 层完成过滤
真正卡住十亿画像系统响应速度的,从来不是算法理论,而是 ID 映射是否连续、存储是否分区、SQL 是否收敛到 bitmap 原语这三个实操细节。
BitSetBitSetBitSetBitSetBitSetand()or()RoaringBitmapBitSetRoaringBitmapArrayContainerBitmapContainerRunContainerand()or()xor()andNot()roaringbitmapAggregateFunction(groupBitmap, UInt32)bitmapAnd()roaringbitmapCREATE TABLE user_tags (tag_id INT, bitmap roaringbitmap)rb_build()SELECT rb_cardinality(rb_and(bitmap, (SELECT bitmap FROM user_tags WHERE tag_id = 123))) FROM user_tags WHERE tag_id = 456tag_idrb_and()RoaringBitmap[0, 10^9)rb_cardinality()rb_iterate()rb_contains()