clickhouse MergeTree序列表引擎

1、MergeTree

1.1数据TTL

1.1.1列级别TTL

-- 创建一张包含列级别TTL的表
create table test.ttl_table_v1 ( \
id String, \ -- 主键字段不能被声明TTL表达式
create_time DateTime, \  -- 需要依托对某个DateTime或Date类型字段的INTERVAL操作,来表达TTL的过期时间
code String TTL create_time + INTERVAL 10 SECOND, \ -- 当系统时间超过该TTL超时时间则字段会被清理并且设置为字段类型的默认值
type UInt8 TTL create_time + INTERVAL 10 SECOND \
)ENGINE = MergeTree() \
PARTITION BY toYYYYMM(create_time) \
ORDER BY id;

insert into table test.ttl_table_v1 values('A000', now(), 'C1', 1), \
('A000', now()+ INTERVAL 10 MINUTE, 'C1', 1);

select * from test.ttl_table_v1;

-- 执行强制触发TTL清理
optimize table test.ttl_table_v1 final;

-- 修改表的TTL字段或者新增TTL字段
alter table test.ttl_table_v1 modify column code string TTL create_time + INTERVAL 1 DAY

1.1.2表级别TTL

create table test.ttl_table_v2 ( \
id String, \
create_time DateTime, \
code String TTL create_time + INTERVAL 10 SECOND, \
type UInt8 \
)ENGINE = MergeTree() \
PARTITION BY toYYYYMM(create_time) \
ORDER BY create_time \
-- 这里定义表级别的TTL,当出发TTL清除的时候,满足过期时间的数据行将会被删除
TTL create_time + INTERVAL 1 DAY;

-- 新增或者修改表级别的TTL
alter table test.ttl_table_v1 modify TTL create_time + INTERVAL 3 DAY

1.1.3TTL运行机制

当设置表级别的TTL后,在写入数据的时候,会以数据分区为单位,在每个分区的目录里面生成一个ttl.txt的文件

[root@localhost test]# tree ttl_table_v2/
ttl_table_v2/
├── 202103_1_1_0
│   ├── checksums.txt
│   ├── columns.txt
│   ├── count.txt
│   ├── data.bin
│   ├── data.mrk3
│   ├── default_compression_codec.txt
│   ├── minmax_create_time.idx
│   ├── partition.dat
│   ├── primary.idx
│   └── ttl.txt
├── detached
└── format_version.txt
ttl format version: 1
{"columns":[{"name":"code","min":0,"max":0},{"name":"type","min":0,"max":0}],"table":{"min":1615175460,"max":1615176099}}

"columns":[{"name":"code","min":0,"max":0}]是列级别ttl的描述信息。
"table":{"min":1615175460,"max":1615176099}是表界别的tt描述信息。

min,max分别保存了当前数据分区中,ttl指定日期字段的最小值、最大值分别于INTERVAL表达式计算后的时间戳。

处理逻辑:

1、MergeTree以分区目录为单位,通过ttl.txt文件记录过期时间,并将其作为后续的判断依据。

2、每当写入一批数据,都会基于INTERVAL表达式计算结果为整个分区生成ttl.txt文件。

3、只有在MergeTree合并分区时,才会触发删除TTL过期数据的逻辑。

4、在选择删除分区时,会使用贪婪算法,它的算法规则是尽可能多的找到会最早过期的,同事年纪有事最老的分区(合并次数最多,MaxBlockNum更大的)。

Tips:

1、TTL默认等待合并频率是由MergeTree的merge_with_ttl_timeout参数控制,默认是一天。如果这个值被设置过小会有性能问题。

2、除了自动出发合并,也可以手动合并

-- 触发一个分区合并
optimize table table_name

-- 触发所有的分区合并
optimize table table_name final

3、目前没有提供删除TTL声明的方法,但是提供了控制全局TTL(不能按找表级别)合并任务的启停方法:

system stop/start ttl merges

 

1.2多路径存储策略

默认策略所有的分区数据会自动保存到config.xml配置的path路径中去。

JBOD策略适合服务器挂载了多块磁盘,但没有做RAID的场景。JBOD(just a bunch of disks)是一种轮询策略,每执行一次INSERT或者MERGE,所产生的新分区会轮询你写入各个磁盘,如果单块磁盘发生故障,则会丢掉应用JBOD策略写入的这部分数据。

HOT/COLD策略适合服务器挂在了不同类型磁盘的场景(SSD/HDD)。将存储分期HOT和COLD两类区域。HOT区域使用SSD,COLD区域使用HDD。数据在写入MerGeTree之初,首先会在HOT区域创建分区目录用于保存数据,当分区数据大小累积到阈值时,数据会自行移动到COLD区域。而在每个区域的内部,也支持定义多块磁盘,所以在单个区域的写入过程中也能应用JBOD策略。

配置存储策略的在config.xml中时storage_configuration标签,下面还有disks(磁盘)和policy(策略)两个标签。

<storage_configuration>
    <disks>
        <!--自定义磁盘名字-->
        <disk_name_a>
            <!--定义磁盘的路径-->
            <path>/clickhouse/data</path>
            <!--定义磁盘的预留空间-->
            <keep_free_space_bytes>4096</keep_free_space_bytes>
        </disk_name_a>
    </disks>
    <policies>
        <!--自定义策略名字-->
        <policy_name_a>
            <volumes>
                <!--卷的自定义名称-->
                <volume_name_a>
                    <!--关联的配置内的磁盘-->
                    <disk>disk_name_a</disk>
                    <disk>disk_name_b</disk>
                    <!--表示在这个卷的单个disk磁盘中,一个数据分区的最大存储阈值,如果当前分区的数据大小超过阈值,
                    则之后的分区会写入下一个disk磁盘-->
                    <max_data_part_size_part>4096</max_data_part_size_part>
                </volume_name_a>
            </volumes>
            <!--如果当前卷的可用空间小于factor因子,并且定义了多个卷,则数据会自定向下一个卷移动-->
            <move_factor>2.0</move_factor>
        </policy_name_a>
    </policies>
</storage_configuration>

1.2.1JBOD策略

<storage_configuration>
    <disks>
        <!--自定义磁盘名字-->
        <disk_hot>
            <!--定义磁盘的路径-->
            <path>/clickhouse/data</path>
            <!--定义磁盘的预留空间-->
            <keep_free_space_bytes>4096</keep_free_space_bytes>
        </disk_hot>
        <disk_cold>
            <!--定义磁盘的路径-->
            <path>/clickhouse/data</path>
            <!--定义磁盘的预留空间-->
            <keep_free_space_bytes>4096</keep_free_space_bytes>
        </disk_cold>
    </disks>
    <policies>
        <!--自定义策略名字-->
        <default_jbod>
            <volumes>
                <!--卷的自定义名称-->
                <jbod>
                    <!--关联的配置内的磁盘-->
                    <disk>disk_hot</disk>
                    <disk>disk_cold</disk>
                </jbod>
            </volumes>
        </default_jbod>
    </policies>
</storage_configuration>

以上就配置好了JBOD存储策略了。在使用之前记得把两个存储目录的拥有者修改为clickhosue的用户,并且需要重启clickhouse-server服务。

执行以SQL可以查看配置的磁盘:

select \
name, \
path, \
formatReadableSize(free_space) as free, \
formatReadableSize(total_space) as total_space, \
formatReadableSize(keep_free_space) as keep_free_space \
from \
system.disks;

执行以下SQL查看配置的存储策略:

select * from system.storage_policies;

使用配置的策略:

-- 创建表
create table test.jbod_table(
id UInt64
)engine = MergeTree() 
Order By id
-- 配置表的存储策略
Setting storage_policy = 'default_jbod'

-- 写入数据
insert into table jbod_table select rand() from numbers(10);

-- 查看分区系统表, 分区存储在哪个磁盘上
select name, disk_name from system.parts where table = 'test.jbod_table';

总结:多个磁盘组成了一个磁盘组,即volume卷。每当生成一个新的数据分区的时候,分区目录会按照volume卷重磁盘定义的顺序,依次轮询写入各个磁盘。

1.2.2HOT/COLD策略

<storage_configuration>
    <disks>
        <!--自定义磁盘名字-->
        <disk_hot>
            <!--定义磁盘的路径-->
            <path>/clickhouse/data</path>
            <!--定义磁盘的预留空间-->
            <keep_free_space_bytes>4096</keep_free_space_bytes>
        </disk_hot>
        <disk_cold>
            <!--定义磁盘的路径-->
            <path>/clickhouse/data</path>
            <!--定义磁盘的预留空间-->
            <keep_free_space_bytes>4096</keep_free_space_bytes>
        </disk_cold>
    </disks>
    <policies>
        <!--自定义策略名字-->
        <moving_from_hot_to_cold>
            <volumes>
                <!--卷的自定义名称-->
                <hot>
                    <!--关联的配置内的磁盘-->
                    <disk>disk_hot</disk>
                    <!--表示在这个磁盘下如果一个分区数据大小超过1M,则需要被移动到紧邻的下一个磁盘卷-->
                    <max_data_part_size>1073741824</max_data_part_size>
                </hot>
                <cold>
                    <disk>disk_cold</disk>
                </cold>
            </volumes>
            <!--如果当前卷的可用空间小于factor因子,并且定义了多个卷,则数据会自定向下一个卷移动-->
            <move_factor>2.0</move_factor>
        </moving_from_hot_to_cold>
    </policies>
</storage_configuration>

使用配置的策略:

-- 创建表
create table test.hot_cold_table(
id UInt64
)engine = MergeTree() 
Order By id
-- 配置表的存储策略
Setting storage_policy = 'moving_from_hot_to_cold'

-- 写入数据
insert into table hot_cold_table select rand() from numbers(10);

-- 查看分区系统表, 分区存储在哪个磁盘上
select name, disk_name from system.parts where table = 'test.hot_cold_table';

Tips:

1、如果一次性写入1MB,分区也会被写入COLD卷中

2、多个磁盘卷组成一个磁盘组volume组,每当生成一个新数据分区的时候,按照阈值大小(max_data_part_size),分区目录会按照volume组中磁盘卷定义的顺序,依次轮询写入各个卷下的磁盘。

3、MergeTree的存储册罗目前不能修改,但是分区目录却可以移动。

-- 将某个分区移动到当前存储策略中当前卷下的其他的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"

2、ReplacingMergeTree

ReplacingMergeTree是为了去重而设计的,能够在分区合并的时候删除重复的数据。

使用时只需要把ENGINE = ReplacingMergeTree(ver),这里的ver是一个选填参数,会指定一个UInt*、Date或者DateTime类型的字段作为版本号,这个参数决定了数据去重时使用的算法。

-- 创建表
create table test.replace_table ( \
id String, \
create_time DateTime, \
code String \
)ENGINE = ReplacingMergeTree() \
PARTITION BY toYYYYMM(create_time) \
ORDER BY (id, code) \
primary key id;

-- 执行以下的命令2次及以上
insert into table test.replace_table values('A111', '2020-03-05 15:00:00', 'C1'),('A111', '2020-04-05 15:00:00', 'C1'),('A111', '2020-05-05 15:00:00', 'C1');

-- 然后手动清理表
optimize table test.replace_table final;

-- 然后查看表数据, 最后主键还是有重复的
select * from test.replace_table

Tips1:ReplacingMergeTree是以分区为单位删除重复数据的。只有在相同的数据分区重复的数据才可以删除,而不同的数据分区之间的重复数据依然不能被删除。

-- 创建表,这里加上版本号字段
create table test.replace_table ( \
id String, \
create_time DateTime, \
code String \
)ENGINE = ReplacingMergeTree(create_time) \
PARTITION BY toYYYYMM(create_time) \
ORDER BY id \
primary key id;

optimize table test.replace_table final;

Tips2:如果加上了版本号字段的话,则在删除重复数据的时候,会保留同一组数据内版本号列字段最大的哪一行。

总结:

(1)使用order by排序键作为判断数据重复的唯一键

(2)只有在合并分区的时候才会触发删除重复数据的逻辑

(3)以数据分区为单位删除重复数据。当分区合并时,同意分区内的重复数据会被删除;不同分区之间的数据不能被删除。

(4)在进行数据去重时,因为分区内的数据已经基于order by进行了排序,所以能够找到那些相邻的重复数据。

(5)数据去重的两种策略:

  • 如果没有设置ver版本号,则保留同一组重复数据中的最后一行。
  • 如果设置了ver版本号,则保留同一组重复数据中ver字段取值最大的那一行。

3、SummingMergeTree

SummingMergeTree的聚合是根据ORDER BY定义的排序键进行的。通常情况下只需要通过定义排序键就能定义了主键,但是如果同时定义了排序键和主键的话,则要求主键列字段必须是排序键的前缀。

当需要修改排序键的时候通过以下的命令:

alter table table_name modify order by (A, B)

注意:在修改排序键的时候,只能在原有的基础上减少字段,如果是新增排序字段的话,那只能是添加通过alter add column新增的字段。

SummingMergeTree使用方法:

ENGINE = SummingMergeTree((col1, col2, …))
Col1, col2为columns参数值,这是一个选填参数,用于设置除主键外的其他数值类型字段,以指定被SUM汇总的列字段。如果不填此参数,则会将所有除主键之外的数值类型进行SUM汇总。

create table summing_table ( \
id String, \
city String, \
v1 UInt32, \
v2 Float64, \
create_time Datetime \
)ENGINE = SummingMergeTree() \
PARTITION BY toYYYYMM(create_time) \
ORDER BY (id, city) \ 
PRIMARY KEY id;

当使用嵌套数据类型的时候:

create table summing_table_nested ( \
id String, \
-- 在使用嵌套数据类型的时候,默认情况下,会以嵌套类型中的第一个字段作为聚合条件Key
-- 如果要使用嵌套中的复合字段作为聚合Key, 则除第一个字段外,任何名称是以Key, Id或Type为后缀结尾的字段,都将和第一个字段一起组成复合Key
nestMap Nested( \
 id UInt32, \
 Key UInt32, \
 val UInt64 \
), \
create_time Datetime \
)ENGINE = SummingMergeTree() \
PARTITION BY toYYYYMM(create_time) \
ORDER BY id;

总结

  1. 用Order BY 排序键作为聚合数据的条件Key
  2. 只有在合并分区的时候才会触发汇总的逻辑
  3. 以数据分区为单位来聚合数据。当分区合并的时,同一数据分区内聚合Key相同的数据会被合并汇总,而不同分区的数据则不会被汇总。
  4. 如果在定义引擎的时候指定了columns汇总列(非主键的数值类型字段),则SUM汇总这些列字段,如果未指定,则聚合所有的非主键的数值类型字段。
  5. 在进行数据汇总的时,同一分区内,相同聚合Key的多行数据会合并成一行。其中,汇总字段会进行SUM计算;对于那些非汇总字段,则会使用第一行数据的取值。
  6. 支持嵌套类型结构,但列字段必须以Map后缀收尾。嵌套类型中默认以第一个字段作为聚合Key。除第一个字段外,任何名称是以Key, Id或Type为后缀结尾的字段,都将和第一个字段一起组成复合Key。

4、AggregatingMergeTree

使用时设置:ENGINE = AggregatingMergeTree() ;

在分区合并时,在每个数据分区内,会按照ORDER BY聚合。而使用何种聚合函数,以及针对那些列字段计算,则是通过定义AggregatingFunction数据类型实现的。

create table agg_table ( \
id String, \
city String, \
-- uniq 去重操作
code AggregateFunction(uniq, String), \
-- sum 求和
value AggregateFunction(sum, UInt32), \
create_time datetime \
) engine = AggregatingMergeTree() \
partition by toYYYYMM(create_time) \
order by (id, city) \
primary key id;

AggregateFunction是clickhouse提供的一种特殊的数据类型,能够以二进制形式存储中间状态结果。

对于AggregateFunction的列字段在写入的时候需要调用*State函数;在查询数据的时候,需要调用相应的*Merge函数,其中*表示定义时使用的聚合函数。

写入的时候需要调用uniq、sum对应的uniqState和sumState函数,并使用INSERT SELECT:

insert into table agg_table
select 'A000', 'wuhan', uniqState('code01'), sumState(toUInt32(100)), '2021-03-06 00:08:00'

查询的时候,需要调用uniqMerge, sumMerge函数:

select id, city, uniqMerge(code), sumMerge(value) from agg_table group by id, city;

AggregatingMergeTree与物化视图结合使用:

  • (1)首先会有一张使用MergeTree的数据明细表作为底表,存储全量的明细数据,并对外提供实时查询。
create table agg_table_basic ( \
id String, \
city String, \
code String, \
value UInt32 \
)ENGINE = MergeTree() \
partition by city \
order by (id, city);
  • (2)在底表的基础上创建一张物化视图,数据写入的时候写入到底表中,数据会自动同步到物化视图,并按照AggragatingMergeTree引擎的规则处理。查询时查询的是物化视图AggragatingMergeTree。
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;

insert into table agg_table_basic values('A000', 'wuhan', 'code1', 100), \
('A000', 'wuhan', 'code2', 200), \
('A000', 'sichuan', 'code1', 150), \
('A000', 'beijing', 'code3', 1000);

select id, city, sumMerge(value), uniqMerge(code) from agg_view group by id, city;

总结:

  1. 用order by 排序键作为数据聚合的条件key
  2. 使用AggregationFunction字段类型定义聚合函数的类型以及聚合的字段。
  3. 只有在分区合并的时候才会触发聚合计算的逻辑。
  4. 以数据分区为单位来聚合数据,当分区合并时,同一数据分区内聚合Key相同的数据会被合并计算,而不同分区的数据则不会被计算。
  5. 在进行数据计算时,因为分区内的数据已经按照排序见排序,所以能找到相邻且拥有相同聚合key的数据。
  6. 在聚合数据时,同一分区内,相同聚合key的多行数据会合并成一行。对于非主键、非AggragateFunction类型字段,则会使用第一行数据的取值。
  7. AggragateFunction类型的字段使用二进制存储。对于AggregateFunction的列字段在写入的时候需要调用*State函数;在查询数据的时候,需要调用相应的*Merge函数,其中*表示定义时使用的聚合函数。
  8. AggragatingMergeTree通常作为物化视图的引擎,与普通MergeTree搭配使用。

5、CollapsingMergeTree

CollapsingMergeTree(折叠合并树)是通过以增代删的思路,支持行级别数据修改和删除的表引擎。

通过定义一个sign标记为字段,记录数据行的状态。如果sign标记为1,则表示这是一行有效的数据;如果sign为-1,这表示这行数据需要被删除。当CollapsingMergeTree分区合并到 时候,同一数据分区内,sign标记为1和-1的一组数据会被抵消删除。

 使用方法:ENGINE = CollapsingMergeTree(sign) 

create table collapse_table 
(
id String, 
code UInt32, 
create_time DateTime, 
-- sign 是一个Int8类型的标志位字段
sign Int8
)ENGINE = CollapsingMergeTree(sign ) 
partition by toYYYYMM(create_time) 
order by id;

 

 

 

猜你喜欢

转载自blog.csdn.net/qq_32323239/article/details/114389167