开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第15天,点击查看活动详情 |
文档参考:《ClickHouse原理解析与应用实践(数据库技术丛书)(朱凯)》
70.【数据库】ClickHouse从入门到放弃-MergeTree的创建方式 - 掘金 (juejin.cn)
71.【数据库】ClickHouse从入门到放弃-MergeTree的存储结构 - 掘金 (juejin.cn)
72.【数据库】ClickHouse从入门到放弃-数据分区 - 掘金 (juejin.cn)
73.【数据库】ClickHouse从入门到放弃-一级索引 - 掘金 (juejin.cn)
74.【数据库】ClickHouse从入门到放弃-二级索引 - 掘金 (juejin.cn)
75.【数据库】ClickHouse从入门到放弃-数据存储 - 掘金 (juejin.cn)
76.【数据库】ClickHouse从入门到放弃-数据标记 - 掘金 (juejin.cn)
77.【数据库】ClickHouse从入门到放弃-对于分区、索引、标记和压缩数据的协同总结 - 掘金 (juejin.cn)
78.【数据库】ClickHouse从入门到放弃-MergeTree 数据TTL - 掘金 (juejin.cn)
MergeTree作为家族系列最基础的表引擎,提供了数据分区、一级索引和二级索引等功能。对于它们的运行机理,前面已经进行了详细介绍。今天进一步学习MergeTree家族独有的另外两项能力——数据TTL与存储策略。
2 多路径存储策略
在ClickHouse 19.15版本之前,MergeTree只支持单路径存储,所有的数据都会被写入config.xml配置中path指定的路径下,即使服务器挂载了多块磁盘,也无法有效利用这些存储空间。为了解决这个痛点,从19.15版本开始,MergeTree实现了自定义存储策略的功能,支持以数据分区为最小移动单元,将分区目录写入多块磁盘目录。
根据配置策略的不同,目前大致有三类存储策略。
·默认策略:MergeTree原本的存储策略,无须任何配置,所有分区会自动保存到config.xml配置中path指定的路径下。
·JBOD策略:这种策略适合服务器挂载了多块磁盘,但没有做RAID的场景。JBOD的全称是Just a Bunch of Disks,它是一种轮询策略,每执行一次INSERT或者MERGE,所产生的新分区会轮询写入各个磁盘。这种策略的效果类似RAID 0,可以降低单块磁盘的负载,在一定条件下能够增加数据并行读写的性能。如果单块磁盘发生故障,则会丢掉应用JBOD策略写入的这部分数据。(数据的可靠性需要利用副本机制保障,这部分内容将会在后面介绍副本与分片时介绍。)
·HOT/COLD策略:这种策略适合服务器挂载了不同类型磁盘的场景。将存储磁盘分为HOT与COLD两类区域。HOT区域使用SSD这类高性能存储媒介,注重存取性能;COLD区域则使用HDD这类高容量存储媒介,注重存取经济性。数据在写入MergeTree之初,首先会在HOT区域创建分区目录用于保存数据,当分区数据大小累积到阈值时,数据会自行移动到COLD区域。而在每个区域的内部,也支持定义多个磁盘,所以在单个区域的写入过程中,也能应用JBOD策略。
存储配置需要预先定义在config.xml配置文件中,由storage_configuration标签表示。在storage_configuration之下又分为disks和policies两组标签,分别表示磁盘与存储策略。
disks的配置示例如下所示,支持定义多块磁盘:
<storage_configuration>
<disks>
<disk_name_a> <!--自定义磁盘名称 -->
<path>/chbase/data</path><!—磁盘路径 -->
<keep_free_space_bytes>1073741824</keep_free_space_bytes>
</disk_name_a>
<disk_name_b>
<path>… </path>
</disk_name_b>
</disks>
其中:
·<disk_name_*>,必填项,必须全局唯一,表示磁盘的自定义名称;
·,必填项,用于指定磁盘路径;
·<keep_free_space_bytes>,选填项,以字节为单位,用于定义磁盘的预留空间。
在policies的配置中,需要引用先前定义的disks磁盘。policies同样支持定义多个策略,它的示例如下:
<policies>
<policie_name_a> <!--自定义策略名称 -->
<volumes>
<volume_name_a> <!--自定义卷名称 -->
<disk>disk_name_a</disk>
<disk>disk_name_b</disk>
<max_data_part_size_bytes>1073741824</max_data_part_size_bytes>
</volume_name_b>
</volumes>
<move_factor>0.2</move_factor>
</policie_name_a>
<policie_name_b>
</policie_name_b>
</policies>
其中:
·<policie_name_*>,必填项,必须全局唯一,表示策略的自定义名称;
·<volume_name_*>,必填项,必须全局唯一,表示卷的自定义名称;
·,必填项,用于关联配置内的磁盘,可以声明多个disk,MergeTree会按定义的顺序选择disk;
·<max_data_part_size_bytes>,选填项,以字节为单位,表示在这个卷的单个disk磁盘中,一个数据分区的最大存储阈值,如果当前分区的数据大小超过阈值,则之后的分区会写入下一个disk磁盘;
·<move_factor>,选填项,默认为0.1;如果当前卷的可用空间小于factor因子,并且定义了多个卷,则数据会自动向下一个卷移动。
在知道了配置格式之后,现在用一组示例说明它们的使用方法。
2.1 JBOD策略
首先,在config.xml配置文件中增加storage_configuration元素,并配置3块磁盘:
<storage_configuration>
<!--自定义磁盘配置 -->
<disks>
<disk_hot1> <!--自定义磁盘名称 -->
<path>/chbase/data</path>
</disk_hot1>
<disk_hot2>
<path>/chbase/hotdata1</path>
</disk_hot2>
<disk_cold>
<path>/chbase/cloddata</path>
<keep_free_space_bytes>1073741824</keep_free_space_bytes>
</disk_cold>
</disks>
…省略
接着,配置一个存储策略,在volumes卷下引用两块磁盘,组成一个磁盘组:
<!-- 实现JDOB效果 -->
<policies>
<default_jbod> <!--自定义策略名称 -->
<volumes>
<jbod> <!—自定义名称 磁盘组 -->
<disk>disk_hot1</disk>
<disk>disk_hot2</disk>
</jbod>
</volumes>
</default_jbod>
</policies>
</storage_configuration>
至此,一个支持JBOD策略的存储策略就配置好了。在正式应用之前,还需要做一些准备工作。首先,需要给磁盘路径授权,使ClickHouse用户拥有路径的读写权限:
sudo chown clickhouse:clickhouse -R /chbase/cloddata /chbase/hotdata1
由于存储配置不支持动态更新,为了使配置生效,还需要重启clickhouse-server服务:
sudo service clickhouse-server restart
服务重启好之后,可以查询系统表以验证配置是否已经生效:
SELECT
name,
path,formatReadableSize(free_space) AS free,
formatReadableSize(total_space) AS total,
formatReadableSize(keep_free_space) AS reserved
FROM system.disks
┌─name─────┬─path────────┬─free────┬─total────┬─reserved─┐
│ default │ /chbase/data/ │ 38.26 GiB │ 49.09 GiB │ 0.00 B │
│ disk_cold │ /chbase/cloddata/ │ 37.26 GiB │ 48.09 GiB │ 1.00 GiB │
│ disk_hot1 │ /chbase/data/ │ 38.26 GiB │ 49.09 GiB │ 0.00 B │
│ disk_hot2 │ /chbase/hotdata1/ │ 38.26 GiB │ 49.09 GiB │ 0.00 B │
└────────┴────────────┴────────┴────────┴───────┘
通过system.disks系统表可以看到刚才声明的3块磁盘配置已经生效。接着验证策略配置:
SELECT policy_name,
volume_name,
volume_priority,
disks,
formatReadableSize(max_data_part_size) max_data_part_size ,
move_factor FROM
system.storage_policies
┌─policy_name─┬─volume_name─┬─disks──────────┬─max_data_part_size─┬─move_factor─┐
│ default │ default │ ['default'] │ 0.00 B │ 0 │
│ default_jbod │ jbod │ ['disk_hot1','disk_hot2']│ 0.00 B │ 0.1 │
└────────┴────────┴─────────────┴──────────┴─────────┘
通过system.storage_policies系统表可以看到刚才配置的存储策略也已经生效了。
现在创建一张MergeTree表,用于测试default_jbod存储策略的效果:
CREATE TABLE jbod_table(
id UInt64
)ENGINE = MergeTree()
ORDER BY id
SETTINGS storage_policy = 'default_jbod'
在定义MergeTree时,使用storage_policy配置项指定刚才定义的default_jbod存储策略。存储策略一旦设置,就不能修改了。现在开始测试它的效果。首先写入第一批数据,创建一个分区目录:
INSERT INTO TABLE jbod_table SELECT rand() FROM numbers(10)
查询分区系统表,可以看到第一个分区写入了第一块磁盘disk_hot1:
SELECT name, disk_name FROM system.parts WHERE table = 'jbod_table'
┌─name─────┬─disk_name
接着写入第二批数据,创建一个新的分区目录:
INSERT INTO TABLE jbod_table SELECT rand() FROM numbers(10)
再次查询分区系统表,可以看到第二个分区写入了第二块磁盘disk_hot2:
SELECT name, disk_name FROM system.parts WHERE table = 'jbod_table'
┌─name─────┬─disk_name─┐
│ all_1_1_0 │ disk_hot1 │
│ all_2_2_0 │ disk_hot2 │
└────────┴───────┘
最后触发一次分区合并动作,生成一个合并后的新分区目录:
optimize TABLE jbod_table
还是查询分区系统表,可以看到合并后生成的all_1_2_1分区,再一次写入了第一块磁盘disk_hot1:
┌─name─────┬─disk_name─┐
│ all_1_1_0 │ disk_hot1 │
│ all_1_2_1 │ disk_hot1 │
│ all_2_2_0 │ disk_hot2 │
└────────┴───────┘
至此,大家应该已经明白JBOD策略的工作方式了。在这个策略中,由多个磁盘组成了一个磁盘组,即volume卷。每当生成一个新数据分区的时候,分区目录会依照volume卷中磁盘定义的顺序,依次轮询并写入各个磁盘。
2.2 HOT/COLD策略
现在介绍HOT/COLD策略的使用方法。首先在上一小节介绍的配置文件中添加一个新的策略:
<policies>
…省略
<moving_from_hot_to_cold><!--自定义策略名称 -->
<volumes>
<hot><!--自定义名称 ,hot区域磁盘 -->
<disk>disk_hot1</disk>
<max_data_part_size_bytes>1073741824</max_data_part_size_bytes>
</hot>
<cold><!--自定义名称 ,cold区域磁盘 -->
<disk>disk_cold</disk>
</cold>
</volumes>
<move_factor>0.2</move_factor>
</moving_from_hot_to_cold>
</policies>
存储配置不支持动态更新,所以为了使配置生效,需要重启clickhouse-server服务:
sudo service clickhouse-server restart
通过system.storage_policies系统表可以看到,刚才配置的存储策略已经生效了。
┌─policy_name────────┬─volume_name┬─disks────┬max_data_part_size─┬─move_factor─┐
│ moving_from_hot_to_cold │ hot │ ['disk_hot1']│ 1.00 MiB │ 0.2 │
│ moving_from_hot_to_cold │ cold │ ['disk_cold']│ 0.00 B │ 0.2 │
└───────────────┴───────┴────────┴──────────┴────────┘
moving_from_hot_to_cold存储策略拥有hot和cold两个磁盘卷,在每个卷下各拥有1块磁盘。注意,hot磁盘卷的max_data_part_size列显示的值是1M,这个值的含义表示,在这个磁盘卷下,如果一个分区的大小超过1MB,则它需要被移动到紧邻的下一个磁盘卷。
与先前一样,现在创建一张MergeTree表,用于测试moving_from_hot_to_cold存储策略的效果:
CREATE TABLE hot_cold_table(
id UInt64
)ENGINE = MergeTree()
ORDER BY id
SETTINGS storage_policy = 'moving_from_hot_to_cold'
在定义MergeTree时,使用storage_policy配置项指定刚才定义的moving_from_hot_to_cold存储策略。存储策略一旦设置就不能再修改。
现在开始测试它的效果,首先写入第一批数据,创建一个分区目录,数据大小500KB:
--写入500K大小,分区会写入hot
INSERT INTO TABLE hot_cold_table SELECT rand()FROM numbers(100000)
查询分区系统表,可以看到第一个分区写入了hot卷:
SELECT name, disk_name FROM system.parts WHERE table = 'hot_cold_table'
┌─name─────┬─disk_name─┐
│ all_1_1_0 │ disk_hot1 │
└────────┴───────┘
接着写入第二批数据,创建一个新的分区目录,数据大小还是500KB:
INSERT INTO TABLE hot_cold_table SELECT rand()FROM numbers(100000)
再次查询分区系统表,可以看到第二个分区,仍然写入了hot卷:
SELECT name, disk_name FROM system.parts WHERE table = 'hot_cold_table'
┌─name─────┬─disk_name─┐
│ all_1_1_0 │ disk_hot1 │
│ all_2_2_0 │ disk_hot1 │
└────────┴───────┘
这是由于hot磁盘卷的max_data_part_size是1MB,而前两次数据写入所创建的分区,单个分区大小是500KB,自然分区目录都被保存到了hot磁盘卷下的disk_hot1磁盘。现在触发一次分区合并动作,生成一个新的分区目录:
optimize TABLE hot_cold_table
查询分区系统表,可以看到合并后生成的all_1_2_1分区写入了cold卷:
┌─name─────┬─disk_name─┐
│ all_1_1_0 │ disk_hot1 │
│ all_1_2_1 │ disk_cold │
│ all_2_2_0 │ disk_hot1 │
└────────┴───────┘
这是因为两个分区合并之后,所创建的新分区的大小超过了1MB,所以它被写入了cold卷,相关查询代码如下:
SELECT
disk_name,
formatReadableSize(bytes_on_disk) AS size
FROM system.parts
WHERE (table = 'hot_cold_table') AND active = 1
┌─disk_name─┬─size────┐
│ disk_cold │ 1.01 MiB │
└────────┴───────┘
注意,如果一次性写入大于1MB的数据,分区也会被写入cold卷。
至此,大家应该明白HOT/COLD策略的工作方式了。在这个策略中,由多个磁盘卷(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'