前言

在OLAP实践中,在有数据更新的场景中,比如存储订单数据,我们经常会用到ReplaceingMergeTree引擎来去重数据,以获取数据的最新状态。但是ReplaceingMergeTree引擎实现数据的去重合并的操作是异步的,这样在实际查询的时候,其实是仍然有一部分数据是未进行合并的。为了保证统计数据的准确性,比如订单金额,一个常用的方法是在查询时增加final关键字。那final关键字是如何合并数据的,以及合并的数据范围是怎样的,本文就对此做一个简单的探索。

知识准备

分片:分片就是clickhouse的实例节点,不同的分片就代表不同的节点或机器,分片之间是物理隔离的 分区:分区是一个表中通过指定的规则划分而成的逻辑数据集,比如日期分区,分区是一种逻辑上的,不同的分片上会有相同的分区

探索过程

探索过程比较长,请大家保持耐心,如果不想看过程,可以直接看结论哈,马上开始~

本文基于的clickhouse版本为version 23.3.1.2823

创建表

创建ReplacingMergeTree引擎的表,分布式表union_order_onl_all_test,本地表union_order_onl_local_test,以日期为分区,order_id作为排序键,mid是消息ID,用消息ID作为数据变更的版本号,同时order_id字段作为分片hash字段,不同的订单会被写入到不同的实例上。

CREATE TABLE gbn_onl_mix.union_order_onl_local_test on cluster lf6ckcnts05
(
`order_id` UInt64 COMMENT '订单号',
`after_prefr_amount_1` Float64 COMMENT '订单金额',
`deal_flag` UInt8 COMMENT '成交标识',
`mid` String COMMENT '消息ID',
`update_time` String COMMENT '更新时间',
`ver` UInt64 DEFAULT toUInt64OrZero(mid) COMMENT '版本号',
`dt`Date DEFAULT toDate(update_time) COMMENT '分区'
)
ENGINE = ReplicatedReplacingMergeTree('/clickhouse/lf6ckcnts05/jdob_ha/gbn_onl_mix/lf6ckcnts05/{shard}', '{replica}', ver)
PARTITION BY toYYYYMMDD(dt)
ORDER BY (order_id)
TTL dt + toIntervalDay(7)
SETTINGS storage_policy = 'jdob_ha', index_granularity = 3 CREATE TABLE gbn_onl_mix.union_order_onl_all_test on cluster lf6ckcnts05 as gbn_onl_mix.union_order_onl_local_test
engine=Distributed(lf6ckcnts05, gbn_onl_mix, union_order_onl_local_test, cityHash64(order_id)) ;

数据初始化

初始数据包括2个订单,111和222,初始版本都是0,初始成交状态也都是0,日期是2023-05-28

INSERT into gbn_onl_mix.union_order_onl_all_test (order_id,after_prefr_amount_1,deal_flag,mid,update_time) values ('111',1,0, 0,'2023-05-28'),('222',2,0,0,'2023-05-28');

查询分区信息和数据如下:可以看到数据被写入到了1个分区的2个part中,分区都是20230528,part名都是20230528_0_0_0

知识点详见 https://clickhouse.com/docs/zh/engines/table-engines/mergetree-family/custom-partitioning-key 分区信息有重复是因为lf6ckcnts05集群的配置是有一个副本

验证同分片同分区数据合并

final合并

order_id=111有数据更新,mid变成了1,即插入如下数据

INSERT into gbn_onl_mix.union_order_onl_all_test (order_id,after_prefr_amount_1,deal_flag,mid,update_time) values ('111',1,0, 1,'2023-05-28');

查询分区信息如下,可见增加了一个part,分区为20230528,part名为20230528_1_1_0

查询数据如下,可见order_id=111的订单,版本0和版本1的数据都是存在的

SELECT * FROM gbn_onl_mix.union_order_onl_all_test WHERE dt = '2023-05-28'

查询数据使用final结果如下,可见order_id=111的订单,只查询出最新版本1的数据

SELECT * FROM gbn_onl_mix.union_order_onl_all_test final WHERE dt = '2023-05-28'

再查询一下实际的数据如下,结果order_id=111的2个版本的数据还是都被查询出来了,可见final查询对实际物理数据的存储没有影响

SELECT * FROM gbn_onl_mix.union_order_onl_all_test WHERE dt = '2023-05-28'

小结:final可以合并同分片同分区的数据,并且final合并数据只是针对当次查询,不会对数据进行物理合并

引擎合并

order_id=111有数据更新,mid变成了2,即插入如下数据

INSERT into gbn_onl_mix.union_order_onl_all_test (order_id,after_prefr_amount_1,deal_flag,mid,update_time) values ('111',1,0, 2,'2023-05-28');

查询分区和数据如下,分区20230528,增加一个part,名为20230528_2_2_0

order_id=111有数据更新,mid变成了3,即插入如下数据

INSERT into gbn_onl_mix.union_order_onl_all_test (order_id,after_prefr_amount_1,deal_flag,mid,update_time) values ('111',1,0, 3,'2023-05-28');

分区20230528,增加名为20230528_2_2_0的part

此时数据还没有被引擎合并,先去吃个饭吧~

Later For a Moment ~~~

吃饭回来,查询分区,发现数据已经被引擎合并了,合并后的分区为20230528_0_3_1,但是同分区不同分片的数据没有被合并

小结:ReplaceingMergeTree引擎合并数据,合并的是同分片同分区的数据

验证同分片不同分区数据合并

final合并

order_id=111数据继续更新,mid变成了4,即插入如下数据

INSERT into gbn_onl_mix.union_order_onl_all_test (order_id,after_prefr_amount_1,deal_flag,mid,update_time) values ('111',1,1, 4,'2023-05-29');

查询分区和数据如下,可见增加了一个part,分区是20230529,part名为20230529_0_0_0,order_id=111订单数据版本3和版本4同时存储,数据还未合并

使用final查询数据,结果如下,我们会发现,order_id=111的订单在2个分区2023-05-28和2023-05-29中的数据被合并了

SELECT * FROM gbn_onl_mix.union_order_onl_all_test final

小结:final可以跨分区进行合并

引擎合并

order_id=111数据继续更新,mid变成5、6、7,即插入如下数据

INSERT into gbn_onl_mix.union_order_onl_all_test (order_id,after_prefr_amount_1,deal_flag,mid,update_time) values ('111',1,1, 5,'2023-05-29','111',1,1, 6,'2023-05-29','111',1,1, 7,'2023-05-29');

查询分区和数据如下,可见增加part 20230529_1_1_0,只插入了一条最新消息为7的数据,即插入数据时,数据就已经合并了

order_id=111数据继续更新,mid变成8、9,即插入如下数据

INSERT into gbn_onl_mix.union_order_onl_all_test (order_id,after_prefr_amount_1,deal_flag,mid,update_time) values ('111',1,1, 8,'2023-05-29');
INSERT into gbn_onl_mix.union_order_onl_all_test (order_id,after_prefr_amount_1,deal_flag,mid,update_time) values ('111',1,1, 9,'2023-05-29');

查询分区和数据如下,新增part 20230529_2_2_0和20230529_3_3_0

使用final同时查询2个分区数据,以及单独查询单个分区的数据,结果如下,可以看到卡不同的分区,最后合并的结果也不同,(这不是废话嘛~~)

SELECT * FROM gbn_onl_mix.union_order_onl_all_test final

SELECT * FROM gbn_onl_mix.union_order_onl_all_test final WHERE dt = '2023-05-29'

SELECT * FROM gbn_onl_mix.union_order_onl_all_test final WHERE dt = '2023-05-28'

Later For a Moment ~~~

数据合并完成,结果如下,part 20230529_0_0_0、20230529_1_1_0、20230529_2_2_0、20230529_3_3_0变成active=0,合并后part为20230529_0_3_1,但是分区20230508的part 20230528_0_3_1并没有被合并

查询分区数据,结果如下

SELECT * FROM gbn_onl_mix.union_order_onl_all_test WHERE dt = '2023-05-28'
SELECT * FROM gbn_onl_mix.union_order_onl_all_test WHERE dt = '2023-05-29'

小结:无论是从分区信息还是从数据结果来看,ReplaceingMergeTree引擎是不会合并同分片不同分区的数据的

验证不同分片数据合并

final合并

考虑order_id=222的订单数据,金额修改成22以做区分,在不同的分片上插入变更数据,本次插入改用向本地表中插入数据,可达到跨分片实例的效果,如下

order_id=222的订单,mid变成1,即插入如下数据

INSERT into gbn_onl_mix.union_order_onl_local_test (order_id,after_prefr_amount_1,deal_flag,mid,update_time) values ('222',22,0,1,'2023-05-28');

查询数据,发现居然和版本0插入到同一个分片上了

SELECT * FROM gbn_onl_mix.union_order_onl_local_test WHERE dt = '2023-05-28'

再来一次,order_id=222的订单,mid变成2,即插入如下数据

INSERT into gbn_onl_mix.union_order_onl_local_test (order_id,after_prefr_amount_1,deal_flag,mid,update_time) values ('222',22,0,2,'2023-05-28');

查询数据,可见这次数据是插入到了不同的分片实例上

SELECT * FROM gbn_onl_mix.union_order_onl_local_test WHERE dt = '2023-05-28'

查看目前分区20230528的数据,如下

SELECT * FROM gbn_onl_mix.union_order_onl_all_test WHERE dt = '2023-05-28'

使用final查询结果如下,可见final查询不能合并跨分片的数据,(order_id=222,ver=1和ver=2是存储在不同分片上的数据)

SELECT * FROM gbn_onl_mix.union_order_onl_all_test final WHERE dt = '2023-05-28'

引擎合并

手动触发引擎合并,如下

optimize table union_order_onl_local_test on cluster lf6ckcnts05 FINAL;

查询数据结果,如下,结果同final查询

小结:无论是final查询还是引擎合并,不同分片上的数据都不会被合并,即使是同分区的也不会被合并

结论

啰哩啰嗦这么多,总结一下吧~~

1.对于不同分片上的数据来说,ReplaceingMergeTree引擎合并和查询时加final的合并,都不会合并不同分片上的数据

2.对于相同分片上的数据来说,ReplaceingMergeTree引擎合并,只合并同分区的数据,不同分区的数据不会合并;查询时加final的合并,会对不同分区的数据进行合并,合并是按照排序键进行合并的,如果想避免不同分区间的合并可以在排序键中增加分区字段

如有问题请指正,欢迎大家沟通交流,感谢~~

作者:京东零售 曹建奇

来源:京东云开发者社区

【Clickhouse】ReplaceingMergeTree引擎final实现合并去重探索的更多相关文章

  1. Clickhouse表引擎探究-ReplacingMergeTree

    作者:耿宏宇 1 表引擎简述 1.1 官方描述 MergeTree 系列的引擎被设计用于插入极大量的数据到一张表当中.数据可以以数据片段的形式一个接着一个的快速写入,数据片段在后台按照一定的规则进行合 ...

  2. Clickhouse表引擎之MergeTree

    1.概述 在Clickhouse中有多种表引擎,不同的表引擎拥有不同的功能,它直接决定了数据如何读写.是否能够并发读写.是否支持索引.数据是否可备份等等.本篇博客笔者将为大家介绍Clickhouse中 ...

  3. ClickHouse MergeTree引擎

    Clickhouse 中最强大的表引擎当属 MergeTree (合并树)引擎及该系列(*MergeTree)中的其他引擎. MergeTree 系列的引擎被设计用于插入极大量的数据到一张表当中.数据 ...

  4. PHP数组合并+与array_merge的区别分析 & 对多个数组合并去重技巧

    PHP中两个数组合并可以使用+或者array_merge,但之间还是有区别的,而且这些区别如果了解不清楚项目中会要命的! 主要区别是两个或者多个数组中如果出现相同键名,键名分为字符串或者数字,需要注意 ...

  5. 两个List合并去重

    今天遇到一个合并去重问题,从网上搜索一样总结出来两个比较简单的方法,这里去重是只能取出地址相同的数据,例如:如果两个字符串的值相同但都是单独new出来的这样去不了 @Test public void ...

  6. linux shell文件合并 去重 分割

    1,合并+去重+分割 转载:shell 文件合并,去重,分割 - kakaisgood - 博客园 (cnblogs.com) 第一:两个文件的交集,并集前提条件:每个文件中不得有重复行1. 取出两个 ...

  7. clickhouse核心引擎MergeTree子引擎

    在clickhouse使用过程中,针对数据量和查询场景,MergeTree是最常用也是较为合适的表引擎.针对特定的业务,MergeTree的子引擎可以针对不同的业务而定,但都基于MergeTree引擎 ...

  8. UniqueMergeTree:支持实时更新删除的 ClickHouse 表引擎

    UniqueMergeTree 开发的业务背景 首先,我们看一下哪些场景需要用到实时更新. 我们总结了三类场景: 第一类是业务需要对它的交易类数据进行实时分析,需要把数据流同步到 ClickHouse ...

  9. clickhouse在风控-风险洞察领域的探索与实践

    一.风险洞察平台介绍 以Clickhouse+Flink实时计算+智能算法为核心架构搭建的风险洞察平台, 建立了全面的.多层次的.立体的风险业务监控体系,已支撑欺诈风险.信用风险.企业风险.小微风险. ...

  10. ClickHouse(07)ClickHouse数据库引擎解析

    目录 Atomic 建表语句 特性 Table UUID RENAME TABLES DROP/DETACH TABLES EXCHANGE TABLES ReplicatedMergeTree in ...

随机推荐

  1. pandas之索引操作

    索引(index)是 Pandas 的重要工具,通过索引可以从 DataFame 中选择特定的行数和列数,这种选择数据的方式称为"子集选择".在 Pandas 中,索引值也被称为标 ...

  2. Tkinter库的使用

    from tkinter import *import tkinter as tkfrom tkinter import Tk, Label,ttkfrom PIL import Image, Ima ...

  3. 【Diary】JZSC 2020 旅 游 记(迫真

    Day-2 期末考试的day1. 科目是数学 政治 语文.数学25.(3)没动.政治各种抄选择题选项()语文难得写完了.作文压根不知道写的什么 Day-1 期末考试的day2. 科目是英语 物理 历史 ...

  4. Java代理之jdk动态代理+应用场景实战

    本文将先介绍jdk动态代理的基本用法,并对其原理和注意事项予以说明.之后将以两个最常见的应用场景为例,进行代码实操.这两个应用场景分别是拦截器和声明性接口,它们在许多开发框架中广泛使用.比如在spri ...

  5. 你知道Vue响应式数据原理吗

    1. Vue2的响应式式原理主要是通过Object.defineProperty的方法里面的setter和getter方法的观察者模式来实现.也就是在组件的初始话阶段给每一个data属性都注册一个se ...

  6. DRF版本控制(源码分析)

    DRF中版本控制的五种情况(源码分析) 在restful规范中要去,后端的API中需要体现版本. drf框架中支持5种版本的设置. 1. URL的GET参数传递(*) 示例: user/?versio ...

  7. Cron表达式介绍与示例

      1. 概念介绍 Cron表达式是一个具有时间含义的字符串,字符串以5~6个空格隔开,分为6~7个域,格式为X X X X X X X.其中X是一个域的占位符.最后一个代表年份的域非必须,可省略.单 ...

  8. .NET实现解析字符串表达式

    一.引子·功能需求 我们创建了一个 School 对象,其中包含了教师列表和学生列表.现在,我们需要计算教师平均年龄和学生平均年龄. //创建对象 School school = new School ...

  9. java LocalDateTime的使用

    1.LocalDateTime的基本使用 //获取当前时间 LocalDateTime localDateTime = LocalDateTime.now(); System.out.println( ...

  10. ts中接口

    前言:ts定义接口的任意一个属性 interface IPerson { name: string age: number family?: any[] // Error,因为不是任意类型的子集 [p ...