StarRocks优化统计分析
业务需求:统计广告数据,生成流量漏斗,查看广告曝光、点击效果。
StarRocks原表结构:
CREATE TABLE `ad_events` (
`event_time` DATETIME NOT NULL COMMENT '时间',
`event_time_hour` DATETIME NOT NULL COMMENT '时间(分、秒为0)',
`event_type` TINYINT(4) NOT NULL COMMENT '事件类型(1:请求,2:展示,3:点击)',
`user_id` INT(11) NOT NULL COMMENT '用户ID(映射为整数)',
`jh_more` TINYINT(4) NOT NULL COMMENT '是否聚合',
`ad_site_id` VARCHAR(128) NOT NULL COMMENT '代码位id',
`client_type` TINYINT(4) NOT NULL COMMENT '客户端类型',
`my_app_id` VARCHAR(64) NOT NULL COMMENT '应用id',
`ad_type` VARCHAR(32) NOT NULL COMMENT '广告类型',
`platform` TINYINT(4) NOT NULL COMMENT '广告源',
`device_id` VARCHAR(64) NOT NULL COMMENT '设备号'
)
COLLATE='utf8_general_ci'
ENGINE=OLAP
PARTITION BY date_trunc('month', event_time) # 按月分区
DISTRIBUTED BY HASH(`user_id`) BUCKETS 100
PROPERTIES (
"compression" = "LZ4", # 存储算法
"fast_schema_evolution" = "true",
"partition_live_number" = "12", # 保留12个月数据,就是1年的数据
"replicated_storage" = "true",
"replication_num" = "3" # 3个副本
);
统计分析sql:
SELECT event_type, COUNT(event_type) AS event_type_nums, COUNT(DISTINCT device_id) AS dau_nums
FROM ssp_ad_events
WHERE user_id = 10077 -- 动态参数AND event_time >= '2025-07-01 00:00:00' -- 动态参数AND event_time <= '2025-07-01 23:59:59' -- 动态参数
GROUP BY event_type;
存在问题:
1、分区字段未有效利用
使用了 event_time精确过滤,但表是按 date_trunc('month', event_time)按月分区,无法利用分区裁剪,会扫描多个分区。
PARTITION BY date_trunc('month', event_time) -- 按月分区
2、COUNT(DISTINCT)函数
COUNT(DISTINCT) 是计算密集型操作,尤其当 device_id 数量很大时,比如上百万个不同设备访问,性能开销非常高。
优化方式:
1、将分区字段改为按天分区
PARTITION BY date_trunc('day', event_time) -- 按天分区
2、COUNT(DISTINCT device_id) —— 性能瓶颈优化
当 device_id是字符串类型 VARCHAR(64),并且有大量不同设备访问时,StarRocks 会对所有值进行去重计算。
这是一个高CPU、高内存、低效的操作,尤其在大规模数据下,可能消耗数秒甚至更长时间。
使用 Bitmap 类型 + BITMAP_UNION_COUNT 实现高性能去重统计。
优化后的表结构:
CREATE TABLE `ad_events_optimized` (event_time DATETIME NOT NULL COMMENT '事件时间',event_time_hour DATETIME NOT NULL COMMENT '小时粒度时间(分秒归零)',event_type TINYINT NOT NULL COMMENT '事件类型',user_id INT NOT NULL COMMENT '用户ID',jhn_more TINYINT NOT NULL,ad_site_id VARCHAR(128) NOT NULL,client_type TINYINT NOT NULL,my_app_id VARCHAR(64) NOT NULL,ad_type VARCHAR(32) NOT NULL,platform TINYINT NOT NULL,device_id VARCHAR(64) NOT NULL, -- 使用 BIGINT 存储哈希值(避免冲突)device_id_hash BIGINT, -- BITMAP 列(用于聚合去重)device_bitmap BITMAP BITMAP_UNION
)
PARTITION BY date_trunc('day', event_time)
DISTRIBUTED BY HASH(user_id) BUCKETS 100
PROPERTIES ("replication_num" = "3","compression" = "LZ4"
);
优化后的sql:
SELECT event_type, COUNT(event_type) AS event_type_nums, bitmap_union_count(device_bitmap) AS dau_nums -- 优化的地方
FROM ssp_ad_events
WHERE user_id = 10077AND event_time >= '2025-07-01 00:00:00'AND event_time <= '2025-07-01 23:59:59'
GROUP BY event_type;
插入数据时生成 Bitmap:
-- INSERT 时直接生成
INSERT INTO ad_events_optimized (event_time, event_time_hour, event_type, user_id, jhn_more, ad_site_id, client_type, my_app_id, ad_type, platform, device_id, device_id_hash, device_bitmap
)
VALUES
('2025-07-01 10:30:00', '2025-07-01 10:00:00', 1, 10077, 1, 'site_001', 2, 'app_123', 'banner', 1, 'device_abc123',bitmap_hash('device_abc123'), -- 自动生成哈希to_bitmap(bitmap_hash('device_abc123')) -- 转为 Bitmap
);
bitmap_hash输出的是64位整数,十亿级数据冲突概率极低。
优化后性能提升10倍以上,在十亿级数据量下能接近秒级响应(BE节点的资源要给够)~
扩展:
1、使用物化视图:物化视图适合固定单参数,由于业务存在动态参数,暂时无法使用。
2、使用HLL:近似去重,误差在1%~10%。
参考文档:
使用 Bitmap 实现精确去重 | StarRocks
BITMAP | StarRocks
bitmap_hash | StarRocks
bitmap_union_count | StarRocks
使用 HyperLogLog 实现近似去重 | StarRocks
同步物化视图 | StarRocks
告别 Count Distinct 慢查询:StarRocks 高效去重全攻略