ck中去重主要是借助ReplacingMeregeTree引擎,它能够在合并part的时候将主键(既排序键)相同的记录只保留一条,但是使用的过程中存在两个问题:
- 数据是在分区part合并的时候去重的,所以要实现全局去重,必须保证主键相同的记录在一个节点同一个分区上。
- ReplacingMergeTree引擎的merge是后台线程不定期触发执行的,时机是不可控的,所以并不能保证多久后不会出现重复数据,正对实时实时性高的用户不瞒住需求。
目前三种解决方案
方案一:ReplacingMergeTree+定时脚本
该方案是目前运用最广,性能最好的。是通过在明细表上出啊昂见ReplacingMergeTree引擎的物化视图,指定ORDER BY排序键作为判断重复数据的唯一键,选择数据去重的策略,默认保留最新的一条
CREATE MATERIALIZED VIEW rt_dw.dwd_cx_trd_order_base_rt_view_local
ENGINE = ReplicatedReplacingMergeTree('/clickhouse/tables/{share}/rt_dw/dwd_cx_trd_order_base_rt_view_local')
PARTITION BY toYYYYMMDD(toDateTime(create_time))
ORDER BY sub_order_id
SETTINGS index_granularity = 8192,storage_policy = 'all_disk' AS
SELECT *
FROM rt_dw.dwd_cx_trd_order_base_rt_local
针对问题1,需要借助分布式表写入能力,可以设置根据主键哈希将数据分布到每个share上。这样就可以保证主键相同的记录在同一个节点上,既将明细表的写入策略将rand改为hash排序键。
CREATE TABLE IF NOT EXISTS rt_dw.dwd_cx_trd_order_base_rt(
'number' Int32 COMMENT '商品数量',
'sub_order_id' String COMMENT '子订单号',
'total_price' Nullable(Int32) COMMENT '子订单号总价格'
) ENGIINE = Distributed(
'chengxin02',
'rt_dw',
'dwd_cx_trd_order_base_rt_view_local',
xxHash32(sub_order_id)
)
指针问题2,通过定时任务定时的触发物化视图的merge,来保证重复数据在一定的误差内。
存在问题:
- 必须写分布式表,但是分布式表的写入性能较差,同时在集群扩容缩容的时候,share数量的变化,可能会导致当天分区主键相同的数据落到不同的节点上,部分数据不能去重
- 明细表必须没有数据,原因是默认的写入策略是随机的。
- 需要定时触发合并的表多了,会增加集群压力
- 指定的去重主键并非是用户后续查询过滤的条件,浪费了排序索引
方案二:ReplacingMergeTree+FINAL
该方案在高qps的环境下,性能较差。做法和方案一一致,创建RepacingMegeTree引擎的物化视图,查询的时候带上FINAL。
存在的问题:
- 当过滤出来的数据量大的时候,单个查询CPU消耗增大,耗时增大。
- 需要写分布式表,尽量让数据在后台去重,减少查询是merge的数据量
- 指定的去重主键并非是用户后续查询的过滤条件,浪费排序索引
- FINAL内存中去重不会落盘,会造成大量的重复计算
方案三:MERGETREE+argMax
argMax是ck提供的聚合函数,既argMax(field1,field2),会按照field1和field2的最大值取field1的值,既可以在查询的时候实现去重表的功能,保留某个字段值最大的一条记录。
表引擎用MergeTree就可以了,查询的时候在子查询里面去重。
该方案的优势:
- 不需要写分布式表,不会存在扩容等问题
- 建表的时候不需要指定去重主键,可以跟合理的使用排序索引
- 可以借助排序索引快速过滤需要的数据,只针对过滤出来的数据进行去重
select
sum(number),
sum(total_price)
from (
select
sub_order_id,
argMax(leader_uid,status) leader_uid,
argMax(number,status) number,
argMax(total_price,status) total_price
from rt_dw.dwd_cx_trd_order_base_rt
where leader_uid in (639312313123123,63979321731931)
group by sub_order_id
)t;
存在问题:
未命中索引,需要group by 大量key,性能极差,cpu和耗时都很高,存在大量的重复计算。