
ClickHouse中最常用也最强大的表引擎就是合并树,即MergeTree,以及该合并树家族下的系列引擎(*MergeTree)。下面对该系列的常见引擎窥其一二。
1. MergeTree家族
1.1 系列引擎
clickhouse的MergeeTree家族中除了基础表引擎MergeTree之外,常用的表引擎还有:
ReplacingMergeTree
SummingMergeTree
AggregatingMergeTree
CollapsingMergeTree
VersionedCollapsingMergeTree
ReplacingMergeTree
SummingMergeTree
AggregatingMergeTree
CollapsingMergeTree
VersionedCollapsingMergeTree
这些表引擎都属于MergeTree家族,名称都以MergeTree后缀结尾。相比于MergeTree引擎,它们的所有特殊逻辑,都是在触发合并的过程中被激活的。
1.2 数据TTL定义
clickhouse支持TTL,即Time To Live,存活时间。具体分为列级别的TTL和表级别的TTL。
列级别的ttl直接在定义列名时加上TTL及时间:
c0 DateTime,
c1 String TTL c0 + INTERVAL 10 SECOND
列级别的ttl修改:
alter table new_table modify column c1 String TTL c0 + INTERVAL 10 SECOND
* 无法取消列级别的ttl
表级别的ttl直接在定义表结构时加上TTL及时间:
order by create_time
TTL create_time + INTERVAL 1 DAY
* 同样,无法取消表级别的TTL
1.3 数据TTL原理
MergeTree以分区目录为单位,通过ttl.txt文件记录过期时间。
每写入一批数据,会基于INTERVAL表达式的计算结果为这个分区生成ttl. txt文件。
只有在MergeTree合并分区时,才触发删除TTL过期数据。
删除分区使用贪婪算法,尽可能找到会最早过期的,同时年纪又是最老的分区(合并次数更多,MaxBlockNum更大的)。
整列TTL过期合并后不包含该列.bin文件。
TTL合并频率由MergeTree的merge_with_ttl_timeout参数控,默认1天。维护独立的TTL任务队列,值过小则性能损耗。
主动强制触发合并:
optimize table new_table //一个分区
optimize table new_table final //所有分区
全局TTL合并任务开关:system stop/start TTL merges
1.4 多路径存储策略
目前clickhouse支持三种数据存储策略的设置:
默认策略:
config.xml配置中path指定的路径。
JBOD策略:
适合挂载了多块磁盘,但没有做RAID的场景。全称Just a Bunch of Disks,轮询策略,每次INSERT或者MERGE,新分区会轮询写入各个磁盘。降低磁盘负载,提高并行读写性能。单磁盘容错机制。
HOT/COLD策略:
适合挂载了不同类型磁盘场景。冷热分区,hot使用ssd,cold使用hdd。写入MergeTree是保存在hot,数据累计后自行移动到cold。hot、cold区域内可定义多磁盘实行jbod策略。具体的工作方式是由多个磁盘卷(volume卷)组成了一个volume组。每当生成一个新数据分区的时候,按照阈值大小(max_data_part_size),分区目录会依照volume组中磁盘卷定义的顺序,依次轮询并写入各个卷下的磁盘。
* 自定义
目前的MergeTree的存储策略目前不支持修改,但是分区目录却支持移动。可以根据具体的数据,以数据分区为最小移动单元,将分区目录写入多块磁盘目录。
将某个分区移动至当前存储策略中当前volume卷下的其他disk磁盘:
alter table hot_cold_table move part 'all_1_2_1' to disk 'disk_hot1';
将某个分区移动至当前存储策略中其他的volume卷:
alter table hot_cold_table move part 'all_1_2_1' to volume 'cold';
clickhouse支持数据库中查看磁盘策略,查询系统表system.storage_policies,其中的max_data_part_size参数表示分区大小限制,超过该值移动到紧邻的下一个磁盘。查询系统表system.parts,其中的name参数表示分区名;disk_name参数表示分区所在的磁盘。
2. 各个击破
本节详细介绍MergeTree家族中常见的表引擎。
2.1 ReplacingMergeTree
MergeTree 引擎中表的主键是不唯一的,为了使得主键唯一,出现了ReplacingMergeTree 表引擎。ReplacingMergeTree 在合并分区时会删除主键相同的重复的数据,且是以分区为单位删除重复数据,在一定程度上达到了去重的效果。
表定义时添加:
engine = ReplacingMergeTree(ver)
其中的ver是选填的版本号,选择数值类的作为版本号,决定去重算法。
原理:
(1)使用ORBER BY排序键作为判断重复数据的唯一键。
(2)只有在合并分区的时候才会触发删除重复数据的逻辑。
(3)以数据分区为单位删除重复数据。当分区合并时,同一分区内的重复数据会被删除;不同分区之间的重复数据不会被删除。
(4)在进行数据去重时,分区内的数据已经基于ORBER BY排序,可以找到那些相邻的重复数据。
(5)数据去重策略有两种:
如果没有设置ver版本号,则保留同一组重复数据中的最后一行;
如果设置了ver版本号,则保留同一组重复数据中ver字段取值最大的那一行。
2.2 SummingMergeTree
当合并 SummingMergeTree 表的数据片段时,ClickHouse 会把所有具有相同主键的行合并为一行,该行包含了被合并的行中具有数值数据类型的列的汇总值。如果主键的组合方式使得单个键值对应于大量的行,则可以显著的减少存储空间并加快数据查询的速度。
首先理清主键和排序键的关系:SummingMergeTree与AggregatingMergeTree的聚合都是根据ORDER BY进行的,同时声明了ORDER BY与PRIMARY KEY, MergeTree会强制要求PRIMARYKEY列字段必须是ORDER BY的前缀。这样就保障了即便在两者定义不同的情况下,主键仍然是排序键的前缀,不会出现索引与数据顺序混乱的问题
ORDER BY是一项关键配置,SummingMergeTree在进行数据汇总时,会根据ORDER BY表达式的取值进行聚合操作。不同分区之间,数据不会被汇总合并。
原理:
(1)用ORBER BY排序键作为聚合数据的条件Key。
(2)只有在合并分区的时候才触发汇总的逻辑。
(3)以分区为单位来聚合数据。当分区合并时,同一数据分区内聚合Key相同的数据会被合并汇总,而不同分区之间的数据则不会被汇总。
(4)如果在定义引擎时指定了columns汇总列(非主键的数值类型字段),则SUM汇总这些列字段;如果未指定,则聚合所有非主键的数值类型字段。
(5)在进行数据汇总时,因为分区内的数据已经基于ORBER BY排序,所以能够找到相邻且拥有相同聚合Key的数据。
(6)在汇总数据时,同一分区内,相同聚合Key的多行数据会合并成一行。其中,汇总字段会进行SUM计算;对于那些非汇总字段,则会使用第一行数据的取值
(7)支持嵌套结构,但列字段名称必须以Map后缀结尾。嵌套类型中,默认以第一个字段作为聚合Key。除第一个字段以外,任何名称以Key、Id或Type为后缀结尾的字段,都将和第一个字段一起组成复合Key
2.3 AggregatingMergeTree
ClickHouse 会将一个数据片段内所有具有相同主键(准确的说是排序键)的行替换成一行,这一行会存储一系列聚合函数的状态。
可以使用该引擎来做增量数据的聚合统计,包括物化视图的数据聚合。类似数据立方体,将规划好的维度数据聚合分析存储,便于快速查询。
该引擎以二进制的形式存储中间状态结果。在合并分区的时候,按照预先定义的条件聚合数据。根据预先定义的聚合函数计算数据并通过二进制的格式存入表内。同时定义ORDER BY与PRIMARY KEY。写入数据时,需要调用*State函数;查询数据时,则需要调用相应的*Merge函数。
常见的应用方式是结合物化视图使用,将它作为物化视图的表引擎。示例:
明细表(底表):
create table agg_table_basic (
id String,
city String,
code String,
value UInt32
)ENGINE = MergeTree()
partition by city
order by (id,city)
物化视图:
create materialized view agg_view
ENGINE = AggregatingMergeTree()
partition by city
order by (id,city)
AS SELECT
id,
city,
uniqState(code) AS code,
sumState(value) AS value
FROM agg_table_basic
GROUP BY id,city
底表负责写入原数据,物化视图负责查询。当底表有新的数据写入时,数据会自动在物化视图中按照引擎规则同步,客户端向物化视图发起查询,获取更高的查询性能。
原理:
(1)用ORBER BY排序键作为聚合数据的条件Key。
(2)使用AggregateFunction字段类型定义聚合函数的类型以及聚合的字段。
(3)只有在合并分区的时候才会触发聚合计算的逻辑。
(4)以数据分区为单位来聚合数据。当分区合并时,同一数据分区内聚合Key相同的数据会被合并计算,而不同分区之间的数据则不会被计算。
(5)在进行数据计算时,因为分区内的数据已经基于ORBER BY排序,所以能够找到那些相邻且拥有相同聚合Key的数据。
(6)在聚合数据时,同一分区内,相同聚合Key的多行数据会合并成一行。对于那些非主键、非AggregateFunction类型字段,则会使用第一行数据的取值。
(7)AggregateFunction类型的字段使用二进制存储,在写入数据时,需要调用*State函数;而在查询数据时,则需要调用相应的*Merge函数。其中,*表示定义时使用的聚合函数。
(8)AggregatingMergeTree通常作为物化视图的表引擎,与普通MergeTree搭配使用。
2.4 CollapsingMergeTree
CollapsingMergeTree 折叠合并书引擎会异步的删除(折叠)这些除了特定列 Sign 有 1 和 -1 的值以外,其余所有字段的值都相等的成对的行。没有成对的行将会被保留。因此,该引擎可以显著的降低存储量并提高 SELECT 查询效率。
在CollapsingMergeTree表中,对行数据的修改删除,会转换成新增操作,以增代删,合并时统一删除。支持行级数据修改和删除的表引擎。定义一个sign标记位字段,记录数据行的状态。sign标记为1,则表示这是一行有效的数据;sign标记为-1,则表示这行数据需要被删除。分区合并时,同一数据分区内,sign标记为1和-1的一组数据会被抵消删除。
建表:
create table collap_table(
id String,
city String,
code String,
sign Int8
)ENGINE = CollapsingMergeTree(sign)
partition by city
order by (id,city)
原理:
(1)如果sign=1比sign=-1的数据多一行,则保留最后一行sign=1的数据。
(2)如果sign=-1比sign=1的数据多一行,则保留第一行sign=-1的数据。
(3)如果sign=1和sign=-1的数据行一样多,并且最后一行是sign=1,则保留第一行sign=-1和最后一行sign=1的数据。
(4)如果sign=1和sign=-1的数据行一样多,并且最后一行是sign=-1,则什么也不保留。
(5)其余情况,ClickHouse会打印警告日志,但不会报错,在这种情形下,查询结果不可预知。
数据不是实时抵消的,是在分区合并时执行。如果需要立即生效可以尝试以下方法:
(1)optimize TABLE table_name FINAL 命令可以强制分区合并,删除旧数据。
(2)改变查询sql:
select
id,
sum(code * sign),
count(code * sign),
avg(code * sign),
uniq(code * sign)
from
collpase_table
group by id
havind sum(sign) > 0
* 相同分区才会抵消,一般数据都会在统一分区。
* 写入顺序有严格限制:
先写入sign=1,再写入sign=-1,正常折叠;
先写入sign=-1,再写入sign=1,不能够折叠;
多线程写入,不能保证顺序,不能保证折叠。
原因:表引擎机制决定的,要求sign=1和sign=-1的数据相邻。
2.5 VersionedCollapsingMergeTree
该引擎的作用同CollapsingMergeTree,相比于CollapsingMergeTree只允许数据严格的连续插入,其没有顺序要求依旧可以正确折叠行。
建表:
create table collap_table(
id String,
city String,
code String,
sign Int8,
ver UInt8
)ENGINE = VersionedCollapsingMergeTree(sign, ver)
partition by city
order by (id,city)
自动将ver作为排序条件并增加到ORDER BY的末端,每个分区内,数据按照ORDER BY id , ver DESC排序。所以无论写入时数据的顺序如何,在折叠处理都能回到正确的顺序。
3. 表引擎关系
本节介绍各引擎之间的关系。
3.1 继承关系
MergeTree表引擎派生出6种引擎:
ReplacingMergeTree
SummingMergeTree
AggregatingMergeTree
CollapsingMergeTree
VersionedCollapsingMergeTree
GraphiteMergeTree
MergeTree表引擎的MergingSortedBlockInputStream,共用MergeTree主体,主要区别在于Merge合并的部分,MergingSortedBlockInputStream的主要作用是按照ORDER BY的规则保持新分区数据的有序性。而其他6种变种MergeTree的合并逻辑,则是在有序的基础之上操作,要么是将排序后相邻的重复数据消除、要么是将重复数据累加汇总,等等。
3.2 组合关系
ReplicatedMergeTree系列是在MergeTree能力的基础之上增加了分布式协同的能力,其借助ZooKeeper的消息日志广播功能,实现了副本实例之间的数据同步功能。以上介绍的7种表引擎都可以加上Replicated前缀,成为分布式表。
* MergeTree引擎告一段落,下期将介绍其他的常见表引擎。
参考资料:
[1] Yandex.clickhouse官方文档[EB/OL]:https://clickhouse.tech/docs/en/
[2] 朱凯.ClickHouse原理解析与应用实践[M]




