我们知道clickhouse最弱的一点就是他的小批量数据的写性能,如果每次insert都是插入几万的数据量,而且是按照分区分好数据的话,clickhouse是能很好的处理这种情况的,但是他非常不擅长处理小批量的数据插入的情况,为了缓解这种小批量数据写入的性能,clickhouse的mergetree加入了类似LSM日志合并树的特性,也就是内存表+write ahead log的方式。

我们首先来看一下这个参数:in_memory_parts_enable_wal 默认为 true,这表明WAL预写日志默认就是开启状态的。所以现在 MergeTree 的写入流程发生了一些变化,类似LSM日志合并树一样,分区目录首先会保存到内存中,并且为了保证数据不丢失,会同步写一份数据到WAL日志中。当数据满足阈值条件(min_rows_for_compact_part 或者 min_bytes_for_compact_part)时,再将数据刷到磁盘。这种方式就是类似Hbase的基于LSM日志结构树存储结构的数据写入方式,对于clickhouse来说,通过这种方式,他就可以先把大量小批量的数据放到内存中,等达到阈值限制的时候就一次性写入到磁盘中,由于是顺序的磁盘写操作,所以性能很高,极大解决了clickhouse的小批量数据的写性能问题.

此外 clickhouse还有另外两个参数(min_rows_for_wide_part || min_bytes_for_wide_part),这两个参数和(min_rows_for_compact_part || min_bytes_for_compact_part)有什么关联呢?

假设有以下的表结构:

CREATE TABLE test_table(
  uid int32,
  name String
  birthday Date
) engine = MergeTree()
PARTITION BY toYYYYMM(birthday)
ORDER BY uid
settings min_rows_for_compact_part = 3,
min_rows_for_wide_part = 5

我们执行如下的sql:

insert into test_table values (1,"xiaoming",'20220910'),(2,"xiaohuang",'20220910')
对于这样一条sql,我们发现分区目录中没有写入任何文件,原因你已经猜到,因为插入的记录的数量小于min_rows_for_compact_part=3的值,所以数据只会写入内存和wal中。
我们继续写入数据并执行optimize table进行数据合并,
insert into test_table values (3,"xiaojun",'20220910'),(4,"xiaogou",'20220910')
由于此时内存中的记录的数量是4条,大于min_rows_for_compact_part=3的阈值,所以此时数据会被刷到磁盘上面形成data.bin/data.mrk的类似日志存储引擎表数据存储结构的文件
我们继续写入数据并执行optimize table进行数据合并,
insert into test_table values (7,"xiaole",'20220910'),(8,"xiaoxie",'20220910')
我们发现此时的数据记录时6条,大于min_rows_for_wide_part=5的阈值,所以此时合并后的数据会形成id.bin/id.mrk,name.bin/name.mrk,birthday.bin/birthday.mrk每一列一个数据文件和标识文件的形式保存在分区目录中
如果直接执行比如insert into test_table values (10,"xiaole",'20220910'),(11,"xiaoxie",'20220910')...记录条数超过min_rows_for_wide_part=5的阈值的话,也会直接形成形成id.bin/id.mrk,name.bin/name.mrk,birthday.bin/birthday.mrk每一列一个数据文件和标识文件的形式保存在分区目录中

clickhouse mergetree引入LSM存储的缺点:
在clickhouse没有引入LSM的内存表之前,我们知道clickhouse数据查询的时候,他会先定位到磁盘上的分区文件,然后定位到磁盘上的每一列数据,就可以得到最终的结果了。但是引入了LSM内存表之后,除了前面从磁盘中的数据读取操作之外,还要合并上LSM内存数中的数据,两者组合起来才是完整的最终数据,所以这里或多或少会有些性能的影响。包括clickhouse服务器的内存需求也会提升.