也没人告诉我 ClickHouse 的 bitmap 还能这么玩……
一、业务背景
1)用户的圈选的规则非常灵活,可能涉及数十种标签或人群包的交并运算,并且要求实时显示计算结果。商业DMP中现有大约数百种标签,hive表中原始数据量PB级,如果采用直接查hive表的方式,显然是无法满足性能要求的。 2)人群包数量多,目前日新增数百个人群包,加上需要刷新的旧人群包,每天需要计算上万个人群包。
1)人群包人数多,从数万到亿。 2)计算量大,目前画像包含数十个维度,每日按人群包X维度计算百分比和tgi排名,涉及数亿次bitmap交并运算。
二、基于bitmap的人群圈选
支持可扩展的,任意维度的标签圈选。目前支持标签数量>300种,总数据量>1PB 打通离线数仓与ClickHouse之间的数据流,实现数仓到ClickHouse和ClickHouse到数仓的双向数据格式转换和传输,以及标签元数据管理机制。 一个包含亿级别的bitmap大小有数百MB,如果只存储在单个bitmap中会导致计算效率下降,因此需要对bitmap的存储方式进行优化。 开发相比原始ClickHouse sql更易于使用的DSL,通过DSL提高系统的易用性,扩展性和执行效率。
SELECTidFROM (SELECT id, unit_id FROM ad_event_table WHEREevent='show' -- 曝光AND unit_id IN (11111, 22222) -- 单元id列表AND log_date >= '${yyyyMMdd, -360d}' -- 近一年GROUP BY unit_id, id -- 按id + unit_id聚合HAVING COUNT(1) > N -- 找出每个单元曝光次数大于N次的id ) t0GROUP BY id -- 所有单元曝光次数>N的id进行去重
SELECT groupBitmapOr(uid_index) AS `uid_index` -- id bitmapFROM tag_index_tableWHERE (tag_name='ad_show') -- 广告曝光AND (log_date='${yyyyMMdd}') -- 每天更新全量数据AND (tag_value IN ('360D-11111','360D-22222')) -- 事先聚合好360天的单元曝光,把范围查询变成几个固定时间区间的点查AND (metric>=N) -- 曝光指标 > N
预聚合数据量大,多个时间窗口存在数据冗余,某些标签要处理每天都要处理近一年的数据,有数百G到TB级,并且30,60,90等不同时间窗口存在重叠,有数据冗余。 标签值x指标值造成标签数据膨胀。指标值(metric)为非离散数据, 如曝光次数,播放时长,指标值可能在0到数千之间分布,假设某个广告单元有1000个指标值,那仅这一个广告单元就会有1000个bitmap(每个bitmap对应一个指标值),这就造成表中的数据行数很多,索引变大,增加了内存和缓存的开销。 圈选方式不灵活。预处理的时间窗口只能处理一些典型的查询,还有很多客户就是要求实时圈选任意日期范围内的指标,对于这部分需求ClickHouse无法满足,只能采用spark离线计算。系统就分成了ClickHouse实时计算和spark离线计算两个部分,增加了系统的复杂度和开发成本。
三、ClickHouse字典服务
四、ClickHouse字典服务在DMP中的应用
字典服务的id是按顺序分配的,可以让id集中到更小的空间里,对于bitmap来说,id越集中,所生成的bitmap体积越小,运算效率越高。 标签圈选不再局限于整数类型的id,通过字典服务把任意字符串映射成一个整数,比如设备号,buvid等等,为将来的产品功能扩充提供了更大的可能性。
为了提高效率,减少线上压力,字典服务每天导出一张离线hive表,业务方先关联hive表获取映射id,如果离线hive表中不存在,则通过RPC实时调用字典服务。字典初始化之后,离线表的映射率通常在90%以上,可以极大的减少线上服务的实时压力。 优化了ClickHouse中不同shard中id的分片算法,每个id是一个64位整数,给定N个分片,则取个id的高48位(17~64位)对N取模,模数相同的id属于通一个bitmap分片。之所以采用这种方法分片,是考虑到ClickHouse bitmap存储的特点,让低16位连续的id尽量处于同一个分片中,因为bitmap中连续的bit位越多,可以采用压缩算法减少体积,提高计算效率。经过测试,在同等条件下,这种优化的分片方式比id直接对分片数N取模,要节省30%的存储,计算速度快一倍以上。
五、BSI原理简介及其在ClickHouse中的功能实现
BSI的slice个数由最大整型值的二进制位数决定 每个slice都是一个bitmap 每个slice对应一个比特位:slice_i 存储第i个比特位上取值为1的metric value对应的所有实体id的集合
CREATE TABLE test.bsi(`log_date` Date,...`ck_bucket` UInt32,`bsi_agg` AggregateFunction(bsi_merge_agg, BSI))ENGINE = AggregatingMergeTreePARTITION BY log_dateORDER BY ck_bucketTTL ...
用于从明细数据构建BSI的bsi_build 用于对单个BSI进行过滤,求和等操作的bsi_filter, bsi_sum, bsi_range, bsi_lt, bsi_gt, bsi_topk, etc 用于对BSI数据列做聚合的bsi_add_agg, bsi_merge_agg, etc
六、BSI+字典服务方案在DMP场景的落地及效果
CREATE TABLE tag_bitmap_bsi(`tag_name` String,`tag_value` String,`log_date` Date,`sp_bucket` UInt32,`sk_bucket` UInt32,`ck_bucket` UInt32,`bsi_agg` AggregateFunction(bsi_merge_agg, BSI))ENGINE = ReplicatedAggregatingMergeTree(...)PARTITION BY (toYYYYMMDD(log_date), tag_name)ORDER BY (sp_bucket, tag_value, ck_bucket)TTL ...SELECT groupBitmapOr(bsi_ge(bsi_agg, N)) AS `uid_index` -- 180天内曝光次数>N次的id组成的bitmapFROM(SELECT bsi_add_agg(bsi_agg) AS `bsi_agg`FROM(SELECT tag_value, bsi_merge_aggMerge(bsi_agg) AS `bsi_agg` -- 一天之内的指标合并(去重)FROMtag_bitmap_index_mapped_bsiWHERE (tag_name = 'ad_show') AND (log_date > '${yyyyMMdd}' - INTERVAL 180 DAY -- 近180天 ) AND (tag_value IN ('11111', '22222'))GROUP BY tag_value, log_date)GROUP BYtag_value -- 最终累加出180天内所有的指标)
在Spark数据同步程序中生成的单个BSI过大,导致spark中的一个row对象过大,在Kryo Serializer做序列化的过程中出现buffer overflow的问题。 写入到ClickHouse各个分片的BSI里的bitmap数据分布稀疏,影响BSI查询性能。 写入到ClickHouse单个分片的BSI的基数过大(即包含的实体id过多),导致单分片BSI的查询性能较低。 ClickHouse中由于分批写入导致存储的BSI个数过大,影响BSI的查询性能。
实现了任意日期的指标人群圈选,不再局限于几个固定的时间窗口,扩展了业务的应用场景。 去除了部分通过spark sql离线计算流程,把所有人群包相关的计算统一到ClickHouse里,简化了系统设计。 对于广告曝光,稿件播放这种数据量大标签时间范围从30天提升到一年,提升了产品的能力。
方案 | 行数 | 大小(压缩前) | 大小(压缩后) |
|---|---|---|---|
| 预聚合bitmap | 663 | 40732860 | 38043990 |
| BSI+字典服务 | 5 | 1790465 | 1753776 |
七、总结与展望
工程化BSI+字典服务方案的数据接入流程,为用户提供更为便利的接入体验,让BSI+字典服务方案赋能更多商业化业务场景。 探索BSI+字典服务的实时链路建设,在低成本低查询延迟的前提下,为用户提供更高的end2end数据时效性。 得益于字典对人群圈选性能的提升,商业化业务正在尝试扩展人群圈选的业务范围,例如投放端定向人数预估,预计24年Q4可以落地。 商业化DMP中尝试BSI的更多应用场景,例如用于多维度广告相关指标人群的画像分析。
引用
BSI Introduction From Hologres(https://www.alibabacloud.com/help/en/hologres/use-cases/profile-analysis-bsi-optimization-beta)
BSI Function Introduction From Hologres(https://www.alibabacloud.com/help/en/hologres/user-guide/bsi-functions)