1. 多路径存储策略介绍
- ClickHouse 19.15 版本之前,Merge 支持支单路径存储,所有数据都被写入 config.xml 配置的 path 指定的路径下,即使挂载多块磁盘也无法有效利用这些空间
- 为了解决这个痛点,19.15 版本实现了自定义存储策略功能,支持以数据分区为最小移动单元,将分区目录写入多个磁盘目录。
2. 配置文件
存储配置需要预先定义在 config.xml 配置文件中,由 storage_configuration 标签标示,在 storage_configuration 之下又分成了 disks 和 policies 两组标签标示,分别表示磁盘与存储策略。
- Disks 配置示例
<storage_configuration>
<disks>
<!--必填,表示自定义磁盘名称,需全局唯一-->
<disk_name_a>
<!--必填,用于指定磁盘路径-->
<path>/clickhouse/data</path>
<!--选填,单位字节 1073741824B= 1,预留磁盘空间-->
<keep_free_space_bytes>1073741824</keep_free_space_bytes>
</disk_name_a>
<disk_name_b>
<path>/clickhouse2/data</path>
<keep_free_space_bytes>1073741824</keep_free_space_bytes>
</disk_name_b>
</disks>
...
- policies 配置示例
<!--定义策略-->
<policies>
<!--必填,自定义策略名称,全局唯一-->
<policie_name_a>
<!---->
<volumes>
<!--必填,自定义卷名称,全局唯一-->
<volume_name_a>
<!--必填,用于关联配置内的磁盘,可以配置多个 disk,Merge 会按照配置的顺序选择磁盘-->
<disk>disk_name_a</disk>
<disk>disk_name_b</disk>
<!--选填,以字节为单位,(下面配置为 1MB)表示在这个卷的单个磁盘中分区的最大存储阈值,如当前分区大于阈值,下个分区会写到其它磁盘中-->
<max_data_part_size_bytes>1048576</max_data_part_size_bytes>
</volume_name_a>
<volume_name_b>
...
</volume_name_b>
</volumes>
<!--选填,默认 0.1 ,如果当前卷的可用空间小于配置数值,并且定义了多个卷,则数据会向下一个卷移动-->
<move_factor>0.2</move_factor>
</policie_name_a>
<policie_name_b>
...
</policie_name_b>
</policies>
</storage_configuration>
3. 策略类型
根据配置策略不同,现在有三种存储策略:
- 默认存储策略
- JBOD 存储策略
- HOT/COLD 存储策略
4. 默认存储策略
MergeTree 原本的存储策略,无需任何配置,所有分区会自动保存到config.xml 配置的 path指定的路径下。
5. JBOD 存储策略
5.1 介绍
- 这种策略适合服务器挂载了多块磁盘,但是没有做 RAID 的场景。
- JBOD 全称为:Just a Bunch of Disks , 它是一种轮询策略,每执行一次 insert 或者 merge ,所产生的的新的分区会轮询写入各个磁盘。
- JBOD 类似 RAID 0 ,可以降低单块磁盘的负载,在一定条件下能够增加数据并行读写的性能。
- 如果单块磁盘发生故障,则会丢掉应用 JBOD 策略写入的这部分数据。
5.2 配置示例
- 修改配置文件
# 切换到配置文件夹
[root@node3 ~]# cd /etc/clickhouse-server
# 编辑 config.xml 配置文件
[root@node3 clickhouse-server]# vim config.xml
<storage_configuration>
<disks>
<!--必填,表示自定义磁盘名称,需全局唯一-->
<disk1>
<!--必填,用于指定磁盘路径-->
<path>/clickhouse/data1/</path>
<!--选填,单位字节 1073741824B= 1,预留磁盘空间-->
<keep_free_space_bytes>1073741824</keep_free_space_bytes>
</disk1>
<disk2>
<path>/clickhouse/data2/</path>
<keep_free_space_bytes>1073741824</keep_free_space_bytes>
</disk2>
</disks>
<!--定义策略-->
<policies>
<!--必填,自定义策略名称,全局唯一-->
<policie_name_a>
<!---->
<volumes>
<!--必填,自定义卷名称,全局唯一-->
<volume_name_a>
<!--必填,用于关联配置内的磁盘,可以配置多个 disk,Merge 会按照配置的顺序选择磁盘-->
<disk>disk1</disk>
<disk>disk2</disk>
</volume_name_a>
</volumes>
<!--选填,默认 0.1 ,如果当前卷的可用空间小于配置数值,并且定义了多个卷,则数据会向下一个卷移动-->
<move_factor>0.2</move_factor>
</policie_name_a>
</policies>
</storage_configuration>
- 给分配的存储目录授权
sudo chown clickhouse:clickhouse -R /clickhouse/data1 /clickhouse/data2
- 重启 clickhouse
# 关闭服务
systemctl stop clickhouse-server.service
# 启动服务
systemctl start clickhouse-server.service
# 验证服务是否启动成功
[root@node3 clickhouse]# netstat -nltp | grep clickhouse
tcp6 0 0 :::9977 :::* LISTEN 19971/clickhouse-se
tcp6 0 0 :::8123 :::* LISTEN 19971/clickhouse-se
tcp6 0 0 :::9004 :::* LISTEN 19971/clickhouse-se
tcp6 0 0 :::9009 :::* LISTEN 19971/clickhouse-se
[root@node3 clickhouse]# ps -aux | grep clickhouse
clickho+ 19970 0.0 0.0 477408 22868 ? Ss 11:35 0:00 clickhouse-watchdog --config=/etc/clickhouse-server/config.xml --pid-file=/run/clickhouse-server/clickhouse-server.pid
clickho+ 19971 1.2 0.8 1395324 270444 ? SLl 11:35 0:03 /usr/bin/clickhouse-server --config=/etc/clickhouse-server/config.xml --pid-file=/run/clickhouse-server/clickhouse-server.pid
root 22516 0.0 0.0 112728 1000 pts/0 S+ 11:40 0:00 grep --color=auto clickhouse
- 检查配置磁盘是否生效
SELECT
name,
path,
formatReadableSize(free_space) AS free,
formatReadableSize(total_space) AS total,
formatReadableSize(keep_free_space) AS keep_free
FROM system.disks
┌─name────┬─path─────────────────┬─free─────┬─total────┬─keep_free─┐
│ default │ /var/lib/clickhouse/ │ 1.61 TiB │ 1.79 TiB │ 0.00 B │
│ disk1 │ /clickhouse/data1/ │ 1.61 TiB │ 1.79 TiB │ 1.00 GiB │
│ disk2 │ /clickhouse/data2/ │ 1.61 TiB │ 1.79 TiB │ 1.00 GiB │
└─────────┴──────────────────────┴──────────┴──────────┴───────────┘
-- 可以看到刚刚配置的两个磁盘已经生效了
- 检查配置策略是否生效
SELECT * FROM system.storage_policies
┌─policy_name────┬─volume_name───┬─volume_priority─┬─disks─────────────┬─volume_type─┬─max_data_part_size─┬─move_factor─┬─prefer_not_to_merge─┐
│ default │ default │ 1 │ ['default'] │ JBOD │ 0 │ 0 │ 0 │
│ policie_name_a │ volume_name_a │ 1 │ ['disk1','disk2'] │ JBOD │ 0 │ 0.2 │ 0 │
└────────────────┴───────────────┴─────────────────┴───────────────────┴─────────────┴────────────────────┴─────────────┴─────────────────────┘
-- 可以看到刚刚的配置也生效了
5.3 使用测试
-- 创建MergeTree表,并指定 JBOD 存储策略
-- 存储策略设置后无法修改
CREATE TABLE t_jbod
(
`id` UInt64
)
ENGINE = MergeTree
ORDER BY id
SETTINGS storage_policy = 'policie_name_a'
-- 写入数据
insert into t_jbod select rand() from numbers(10);
-- 查看分区情况
SELECT
name,
disk_name
FROM system.parts
WHERE table = 't_jbod'
Query id: a76ae2eb-abf9-4809-bc8e-a55f2ccb02f1
┌─name──────┬─disk_name─┐
│ all_1_1_0 │ disk1 │ -- 数据写到 disk1
└───────────┴───────────┘
-- 再次插入数据
insert into t_jbod select rand() from numbers(10);
-- 再次查看分区情况
select name,disk_name from system.parts where table = 't_jbod';
┌─name──────┬─disk_name─┐
│ all_1_1_0 │ disk1 │
│ all_2_2_0 │ disk2 │ -- 数据写到 disk2
└───────────┴───────────┘
-- 合并分区
optimize table t_jbod;
-- 查看分区情况
select name,disk_name from system.parts where table = 't_jbod';
┌─name──────┬─disk_name─┐
│ all_1_1_0 │ disk1 │
│ all_1_2_1 │ disk1 │ -- 合并后的分区写到了 disk1
│ all_2_2_0 │ disk2 │
└───────────┴───────────┘
在 JBOD 策略中,多个磁盘会组成一个磁盘组,即卷,每当生成一个新分区的时候,分区目录会依照卷中定义的磁盘顺序,依次轮询写入。
5.4 测试增加磁盘地址
mkdir /clickhouse/data3
sudo chown clickhouse:clickhouse -R /clickhouse/data3
- 修改配置文件
<storage_configuration>
<disks>
<disk1>
<path>/clickhouse/data1/</path>
<keep_free_space_bytes>1073741824</keep_free_space_bytes>
</disk1>
<disk2>
<path>/clickhouse/data2/</path>
<keep_free_space_bytes>1073741824</keep_free_space_bytes>
</disk2>
<!--增加 disk3 -->
<disk3>
<path>/clickhouse/data3/</path>
<keep_free_space_bytes>1073741824</keep_free_space_bytes>
</disk3>
</disks>
<policies>
<policie_name_a>
<volumes>
<volume_name_a>
<disk>disk1</disk>
<disk>disk2</disk>
<!--增加 disk3 -->
<disk>disk3</disk>
<max_data_part_size_bytes>1073741824</max_data_part_size_bytes>
</volume_name_a>
</volumes>
<move_factor>0.2</move_factor>
</policie_name_a>
</policies>
</storage_configuration>
- 重启 clickhouse
# 关闭服务
systemctl stop clickhouse-server.service
# 启动服务
systemctl start clickhouse-server.service
# 验证服务是否启动成功
[root@node3 clickhouse]# netstat -nltp | grep clickhouse
tcp6 0 0 :::9977 :::* LISTEN 19971/clickhouse-se
tcp6 0 0 :::8123 :::* LISTEN 19971/clickhouse-se
tcp6 0 0 :::9004 :::* LISTEN 19971/clickhouse-se
tcp6 0 0 :::9009 :::* LISTEN 19971/clickhouse-se
[root@node3 clickhouse]# ps -aux | grep clickhouse
clickho+ 19970 0.0 0.0 477408 22868 ? Ss 11:35 0:00 clickhouse-watchdog --config=/etc/clickhouse-server/config.xml --pid-file=/run/clickhouse-server/clickhouse-server.pid
clickho+ 19971 1.2 0.8 1395324 270444 ? SLl 11:35 0:03 /usr/bin/clickhouse-server --config=/etc/clickhouse-server/config.xml --pid-file=/run/clickhouse-server/clickhouse-server.pid
root 22516 0.0 0.0 112728 1000 pts/0 S+ 11:40 0:00 grep --color=auto clickhouse
- 检查配置是否生效并测试
-- 检查磁盘配置
SELECT
name,
path,
formatReadableSize(free_space) AS free,
formatReadableSize(total_space) AS total,
formatReadableSize(keep_free_space) AS keep_free
FROM system.disks
┌─name────┬─path─────────────────┬─free─────┬─total────┬─keep_free─┐
│ default │ /var/lib/clickhouse/ │ 1.61 TiB │ 1.79 TiB │ 0.00 B │
│ disk1 │ /clickhouse/data1/ │ 1.61 TiB │ 1.79 TiB │ 1.00 GiB │
│ disk2 │ /clickhouse/data2/ │ 1.61 TiB │ 1.79 TiB │ 1.00 GiB │
│ disk3 │ /clickhouse/data3/ │ 1.61 TiB │ 1.79 TiB │ 1.00 GiB │
└─────────┴──────────────────────┴──────────┴──────────┴───────────┘
-- 检查策略配置
SELECT * FROM system.storage_policies;
┌─policy_name────┬─volume_name───┬─volume_priority─┬─disks─────────────────────┬─volume_type─┬─max_data_part_size─┬─move_factor─┬─prefer_not_to_merge─┐
│ default │ default │ 1 │ ['default'] │ JBOD │ 0 │ 0 │ 0 │
│ policie_name_a │ volume_name_a │ 1 │ ['disk1','disk2','disk3'] │ JBOD │ 0 │ 0.2 │ 0 │
└────────────────┴───────────────┴─────────────────┴───────────────────────────┴─────────────┴────────────────────┴─────────────┴─────────────────────┘
-- 查看分区情况
select name,disk_name from system.parts where table = 't_jbod';
┌─name──────┬─disk_name─┐
│ all_1_7_2 │ disk2 │
└───────────┴───────────┘
-- 写入数据测试
insert into t_jbod select rand() from numbers(10);
-- 查看分区情况
select name,disk_name from system.parts where table = 't_jbod';
┌─name──────┬─disk_name─┐
│ all_1_7_2 │ disk2 │
│ all_8_8_0 │ disk1 │
└───────────┴───────────┘
-- 写入数据测试
insert into t_jbod select rand() from numbers(10);
-- 查看分区情况
select name,disk_name from system.parts where table = 't_jbod';
┌─name──────┬─disk_name─┐
│ all_1_7_2 │ disk2 │
│ all_8_8_0 │ disk1 │
│ all_9_9_0 │ disk2 │
└───────────┴───────────┘
-- 写入数据测试
insert into t_jbod select rand() from numbers(10);
-- 查看分区情况
select name,disk_name from system.parts where table = 't_jbod';
┌─name────────┬─disk_name─┐
│ all_1_7_2 │ disk2 │
│ all_8_8_0 │ disk1 │
│ all_9_9_0 │ disk2 │
│ all_10_10_0 │ disk3 │
└─────────────┴───────────┘
6. HOT/COLD 存储策略
6.1 介绍
- HOT/COLD 存储策略适用于服务器挂在了不同类型磁盘的场景。
- 将不同类型的磁盘划分为 hot 和 cold 两种类型。
- hot 类型对应的磁盘为 SSD 一类的高性能存储媒介,注重存取的性能。
- cold 类型对应的是 HDD 这类的大容量存储媒介,注重经济性。
- 在数据写入 MergeTree 初期,会到 hot 区域创建分区目录并保存数据,当数据大小累积到阈值时,数据会移动到 cold 区域。
- 在每个分区的内部,也支持多个磁盘,所以在单个区域的写入过程中,也能应用 JBOD 策略
6.2 配置示例
在前面的配置文件基础上增加策略即可
# 切换到配置文件夹
[root@node3 ~]# cd /etc/clickhouse-server
# 编辑 config.xml 配置文件
[root@node3 clickhouse-server]# vim config.xml
<storage_configuration>
<disks>
<disk1>
<path>/clickhouse/data1/</path>
<keep_free_space_bytes>1073741824</keep_free_space_bytes>
</disk1>
<disk2>
<path>/clickhouse/data2/</path>
<keep_free_space_bytes>1073741824</keep_free_space_bytes>
</disk2>
<disk3>
<path>/clickhouse/data3/</path>
<keep_free_space_bytes>1073741824</keep_free_space_bytes>
</disk3>
</disks>
<policies>
<policie_name_a>
<volumes>
<volume_name_a>
<disk>disk1</disk>
<disk>disk2</disk>
<disk>disk3</disk>
</volume_name_a>
</volumes>
<move_factor>0.2</move_factor>
</policie_name_a>
<!--增加策略,自定义策略名称-->
<hot_to_cold>
<volumes>
<!--定义 hot 区磁盘-->
<hot>
<disk>disk1</disk>
<!--设置批次数据量阈值 1MB-->
<max_data_part_size_bytes>1048576</max_data_part_size_bytes>
</hot>
<!--定义 cold 区磁盘-->
<cold>
<disk>disk2</disk>
</cold>
</volumes>
<move_factor>0.2</move_factor>
</hot_to_cold>
</policies>
</storage_configuration>
- 重启 clickhouse
# 关闭服务
systemctl stop clickhouse-server.service
# 启动服务
systemctl start clickhouse-server.service
# 验证服务是否启动成功
[root@node3 clickhouse]# netstat -nltp | grep clickhouse
tcp6 0 0 :::9977 :::* LISTEN 19971/clickhouse-se
tcp6 0 0 :::8123 :::* LISTEN 19971/clickhouse-se
tcp6 0 0 :::9004 :::* LISTEN 19971/clickhouse-se
tcp6 0 0 :::9009 :::* LISTEN 19971/clickhouse-se
[root@node3 clickhouse]# ps -aux | grep clickhouse
clickho+ 19970 0.0 0.0 477408 22868 ? Ss 11:35 0:00 clickhouse-watchdog --config=/etc/clickhouse-server/config.xml --pid-file=/run/clickhouse-server/clickhouse-server.pid
clickho+ 19971 1.2 0.8 1395324 270444 ? SLl 11:35 0:03 /usr/bin/clickhouse-server --config=/etc/clickhouse-server/config.xml --pid-file=/run/clickhouse-server/clickhouse-server.pid
root 22516 0.0 0.0 112728 1000 pts/0 S+ 11:40 0:00 grep --color=auto clickhouse
- 检查配置策略是否生效
SELECT
policy_name,
volume_name,
disks,
volume_type,
formatReadableSize(max_data_part_size) AS max_data_part_size
FROM system.storage_policies
Query id: 0dee9fb6-0fa7-4f46-b282-5b769f1342ef
┌─policy_name────┬─volume_name───┬─disks─────────────────────┬─volume_type─┬─max_data_part_size─┐
│ default │ default │ ['default'] │ JBOD │ 0.00 B │
│ hot_to_cold │ hot │ ['disk1'] │ JBOD │ 1.00 MiB │
│ hot_to_cold │ cold │ ['disk2'] │ JBOD │ 0.00 B │
│ policie_name_a │ volume_name_a │ ['disk1','disk2','disk3'] │ JBOD │ 0.00 B │
└────────────────┴───────────────┴───────────────────────────┴─────────────┴────────────────────┘
-- 可以看到刚刚的配置已经生效
hot_to_cold 策略有 hot 和 cold 两个磁盘卷,每个磁盘卷下面各有一个磁盘
hot 磁盘的 max_data_part_size 设置值为 1MB,当分区大小超过 1MB 的时候,数据会被移动到紧邻的下一个磁盘卷
6.3 使用测试
-- 创建MergeTree表,并指定 JBOD 存储策略
-- 存储策略设置后无法修改
CREATE TABLE t_hot_to_cold
(
`id` UInt64
)
ENGINE = MergeTree
ORDER BY id
SETTINGS storage_policy = 'hot_to_cold'
-- 写入 500KB 数据
insert into t_hot_to_cold select rand() from numbers(100000);
-- 查看分区情况
select name,disk_name from system.parts where table = 't_hot_to_cold';
SELECT
name,
disk_name
FROM system.parts
WHERE table = 't_hot_to_cold'
Query id: fdc909bc-508f-40e6-8eb2-43dd89c78373
┌─name──────┬─disk_name─┐
│ all_1_1_0 │ disk1 │ -- 数据写到 hot 分区(disk1)
└───────────┴───────────┘
-- 再次插入 500KB 数据
insert into t_hot_to_cold select rand() from numbers(100000);
-- 再次查看分区情况
select name,disk_name from system.parts where table = 't_hot_to_cold';
┌─name──────┬─disk_name─┐
│ all_1_1_0 │ disk1 │
│ all_2_2_0 │ disk1 │ -- 再次写到 hot 分区(disk1)
└───────────┴───────────┘
-- 由于 hot 磁盘分区的 max_data_part_size 为 1MB,单个分区小于 1MB 都会直接写入到这个分区中。
-- 合并分区
optimize table t_hot_to_cold;
-- 查看分区情况
select name,disk_name from system.parts where table = 't_hot_to_cold';
┌─name──────┬─disk_name─┐
│ all_1_1_0 │ disk1 │
│ all_1_2_1 │ disk2 │ -- 合并后的分区 数据被移动到了 写到 clod 分区(disk2)
│ all_2_2_0 │ disk1 │
└───────────┴───────────┘
-- 查看分区大小
SELECT
name,
disk_name,
formatReadableSize(bytes_on_disk) AS bytes_on_disk,
active
FROM system.parts
WHERE table = 't_hot_to_cold'
Query id: 2472a34a-a266-444f-83e5-fee0459db187
┌─name──────┬─disk_name─┬─bytes_on_disk─┬─active─┐
│ all_1_1_0 │ disk1 │ 538.88 KiB │ 0 │ -- active = 0 表示分区不活跃了
│ all_1_2_1 │ disk2 │ 1.01 MiB │ 1 │
│ all_2_2_0 │ disk1 │ 538.69 KiB │ 0 │
└───────────┴───────────┴───────────────┴────────┘
-- 当写入数据大于阈值时会自动写入到 cold 分区中
-- 再次插入 1500KB 数据
insert into t_hot_to_cold select rand() from numbers(300000);
-- 查看分区情况
select name,disk_name from system.parts where table = 't_hot_to_cold';
┌─name──────┬─disk_name─┐
│ all_1_2_1 │ disk2 │
│ all_3_3_0 │ disk2 │ -- 刚才写入的数据被写入到 clod 分区(disk2)
└───────────┴───────────┘
7. 手动移动分区
- MergeTree 存储策略不能修改,但是可以手动修改分区所在磁盘
-- 如将某个分区移动到当前卷的其他磁盘
-- 移动前
select name,disk_name from system.parts where table ='t_jbod';
┌─name───────┬─disk_name─┐
│ all_1_10_3 │ disk1 │
└────────────┴───────────┘
-- 移动分区
alter table t_jbod move part 'all_1_10_3' to disk 'disk2';
-- 移动后
select name,disk_name from system.parts where table ='t_jbod';
┌─name───────┬─disk_name─┐
│ all_1_10_3 │ disk2 │
└────────────┴───────────┘
-- 也可以将当前分区移动到其他卷
-- 向 hot 区写入 500KB 数据
insert into t_hot_to_cold select rand() from numbers(100000);
-- 查看移动前分区情况
select name,disk_name from system.parts where table = 't_hot_to_cold';
┌─name──────┬─disk_name─┐
│ all_1_2_1 │ disk2 │
│ all_3_3_0 │ disk2 │
│ all_4_4_0 │ disk1 │
└───────────┴───────────┘
-- 将 all_4_4_0 移动到 cold 卷下
alter table t_hot_to_cold move part 'all_4_4_0' to volume 'cold';
-- 查看移动后分区情况
select name,disk_name from system.parts where table = 't_hot_to_cold';
┌─name──────┬─disk_name─┐
│ all_1_2_1 │ disk2 │
│ all_3_3_0 │ disk2 │
│ all_4_4_0 │ disk2 │
└───────────┴───────────┘
-- 注意因为空间限制,不能向 hot 区域中移动大于阈值的分区,如下所示
alter table t_hot_to_cold move part 'all_1_2_1' to volume 'hot';
Received exception from server (version 21.4.3):
Code: 243. DB::Exception: Received from localhost:9977. DB::Exception: Move is not possible. Not enough space on 'hot'.