ClickHouse新版本开始支持Write-Ahead-Log(WAL) 功能。
介绍 WAL 之前,先重温一下 MergeTree 最基本的合并过程。
MergeTree的高频写问题?
CREATE TABLE test
(
id UInt8,
name String,
age UInt8,
shijian Date)
ENGINE = MergeTree()
PARTITION BY toYYYYMM(shijian)
ORDER BY id;
insert into test values(1001,'张三','18',now());
insert into test values(1002,'李四','28',now());
insert into test values(1003,'王五','38',now());
此时,MergeTree 会生成 3 个分区目录:
手动合并之后的分区目录
对于 ClickHouse MergeTree 引擎,在数据的写入过程中,数据总会以数据片段的形式被写入磁盘,且数据片段不可修改。每一批次的写入(每执行一次INSERT)MergeTree都会按照分区规则在磁盘上生成一个全新的分区目录(part),为了避免片段过多,在未来的某一时刻,属于相同分区的数据片段会被合并成一个全新的分区目录,这种数据分区往复合并的特点正是合并树的名称由来。其中如果有多个客户端,每个客户端写入的数据量较少、次数较频繁的情况下,就会引发以下错误提示。
Too many parts (N). Merges areprocessing significantly slower than inserts.
WAL预写日志解决了这个问题,提高写入性能,在 ClickHouse 的新版本中,MergeTree 多了这么几个参数:
M(SettingUInt64, min_bytes_for_wide_part, 0, xxxxxxxx, 0) \
M(SettingUInt64, min_rows_for_wide_part, 0, xxxxxxxxx, 0) \
M(SettingUInt64, min_bytes_for_compact_part, 0, xxxxx, 0) \
M(SettingUInt64, min_rows_for_compact_part, 0, xxxxxx, 0) \
M(SettingBool, in_memory_parts_enable_wal, true, xxxx, 0) \
M(SettingUInt64, write_ahead_log_max_bytes, 1024 * 1024 * 1024, xxxx, 0) \
其中 in_memory_parts_enable_wal 默认为 true,这说明预写日志默认就是开启状态的。
CREATE TABLE default.test1
(
id UInt8,
name String,
age UInt8,
shijian Date
)
ENGINE = MergeTree()
PARTITION BY toYYYYMM(shijian)
ORDER BY id
SETTINGS min_rows_for_compact_part = 2, index_granularity = 8192;
min_rows_for_compact_part = 2 表示数据首先会被写到内存和 WAL中,当触发 Merge 的时候,如果数据大于 2 行,就直接把合并后的分区写到磁盘。
insert into test1 values(1001,'张三','18',now());
insert into test1 values(1002,'李四','28',now());
insert into test1 values(1003,'王五','38',now());
写入之后还没有触发 Merge 动作,磁盘目录情况:
clickhouse-client -m
optimize table test1;
在此之前,MergeTree 只有一种 wide 布局,也就是每个列字段都拥有一组独立的文件,例如下图所示:
由于现在添加了wal新特性,致使MergeTree的分区布局也得到了扩展,插入过程中,数据首先进入内存,满足阈值之后,会将内存中的数据刷到磁盘。
CREATE TABLE default.test2
(
id UInt8,
name String,
age UInt8,
shijian Date
)
ENGINE = MergeTree()
PARTITION BY toYYYYMM(shijian)
ORDER BY id
SETTINGS min_rows_for_compact_part = 2, min_rows_for_wide_part = 10, index_granularity = 8192;
insert into test2 values(1001,'张三','18',now());
insert into test2 values(1002,'李四','28',now());
insert into test2 values(1003,'王五','38',now());
optimize table test2;
所有的的数据写到了同一个 data.bin 文件中,所有列的标记文件也都写到了同一个.mark文件。当列字段很多,数据又很少的时候,可以考虑使用这种布局模式的分区。
详情了解更多欢迎关注公众号