ClickHouse表引擎 4.MergeTree 多路径存储


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─┐
│ defaultdefault1['default']       │ JBOD        │                  000 │
│ policie_name_a │ volume_name_a │               1['disk1','disk2'] │ JBOD        │                  00.20 │
└────────────────┴───────────────┴─────────────────┴───────────────────┴─────────────┴────────────────────┴─────────────┴─────────────────────┘

-- 可以看到刚刚的配置也生效了

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─┐
│ defaultdefault1['default']               │ JBOD        │                  000 │
│ policie_name_a │ volume_name_a │               1['disk1','disk2','disk3'] │ JBOD        │                  00.20 │
└────────────────┴───────────────┴─────────────────┴───────────────────────────┴─────────────┴────────────────────┴─────────────┴─────────────────────┘


-- 查看分区情况
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─┐
│ defaultdefault['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'.

文章作者: hnbian
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 hnbian !
评论
 上一篇
ClickHouse表引擎 5.MergeTree 家族其它引擎 ClickHouse表引擎 5.MergeTree 家族其它引擎
1. ReplacingMergeTree1.1 介绍 MergeTree 虽然可以设置主键,但是主键没有唯一约束的作用,即使多行主键相同的数据一样能够写入成功,但是在某些场景下,我们希望表中不存在主键重复的数据。 ReplacingMe
2021-06-13
下一篇 
ClickHouse表引擎 3.MergeTree 数据生命周期 ClickHouse表引擎 3.MergeTree 数据生命周期
1 数据 TTL TTL(Time To Live)表示数据的存活时间,在 Merge 中可以为某个字段或者整个表设置TTL。 如果设置列级别的 TTL,那么到期时会删除这一列的数据 如果设置表级别的 TTL,那么到期时会删除整个表的数据
2021-05-28
  目录