我曾在书中介绍过,ReplicatedMergeTree 支持根据 block_id 防止重复的数据写入。ClickHouse 在写入一个 Block 块的时候,会按照当前 Block 的数据顺序、数据行和数据大小等指标,计算 Hash 并生成 block_id。
如果某个待写入的 Block 块,与先前已被写入的 Block 块,拥有相同的 block_id,则该 Block 数据块会被忽略。
这项特性主要是为了解决,由上游数据写入程序 (如 ETL 程序),因为任务失败、网络等问题引起的 retry 任务,导致数据重复写入的问题。
例如,我们用 Spark 做 ETL 向 ClickHouse 写入数据,由于一些原因 Executor上执行写入 task 的进程被杀掉了,此时 Spark 会再起一个进程重新retry执行刚才的写入 task,这就可能会造成数据的重复写入。
利用 ReplicatedMergeTree 按照 block_id 去重的功能,就可以在一定程度上避免类似问题。
在此之前,这项特性是 ReplicatedMergeTree 系列独有的,如果我们使用非副本的 MergeTree 系列,则享受不到这项功能。而在 ClickHouse 的新版本中,普通的 MergeTree 系列也支持这项功能了,是不是很 Happy 呢?接下来,就快速一览如何使用吧。
使用的方法很简单,在创建 MergeTree 的时候,
增加一项 non_replicated_deduplication_window 参数即可:
代码语言:javascript复制CREATE TABLE deduplication_test
(
id UInt64,
value String,
part UInt8 DEFAULT 111
)
ENGINE = MergeTree()
ORDER BY id
PARTITION BY part
SETTINGS non_replicated_deduplication_window=3;
non_replicated_deduplication_window 表示,保存最后多少个 block 的 hash,默认是 0 (不开启)。
现在写入数据:
代码语言:javascript复制INSERT INTO deduplication_test (id, value) VALUES (1, '1');
再写入一次重复的数据:
代码语言:javascript复制INSERT INTO deduplication_test (id, value) VALUES (1, '1');
你会看到日志中出现如下的信息,该 Block ID 已经存在了, 忽略写入:
代码语言:javascript复制<Information> default.deduplication_test: Block with ID 111_5329300851986775609_11764731552300554311 already exists as part 111_1_1_0; ignoring it
查询表数据,重复的数据没有被写入,符合预期:
代码语言:javascript复制SELECT
id,
value,
part
FROM deduplication_test
Query id: abb86e7a-ad9d-4403-819d-5b0c46776c09
┌─id─┬─value─┬─part─┐
│ 1 │ 1 │ 111 │
└────┴───────┴──────┘
1 rows in set. Elapsed: 0.004 sec.
如果我们写入不同的数据,则写入成功:
代码语言:javascript复制INSERT INTO deduplication_test (id, value) VALUES (1, '2'),(1, '3');
SELECT id, value,part FROM deduplication_test;
┌─id─┬─value─┬─part─┐
│ 1 │ 1 │ 111 │
│ 1 │ 2 │ 111 │
│ 1 │ 3 │ 111 │
└────┴───────┴──────┘
block_id 是按分区内去重复的,如果是不同分区,则不起作用:
代码语言:javascript复制INSERT INTO deduplication_test (id, value, part) VALUES (1, '1', 222);
SELECT id, value,part FROM deduplication_test;
┌─id─┬─value─┬─part─┐
│ 1 │ 1 │ 111 │
│ 1 │ 2 │ 111 │
│ 1 │ 3 │ 111 │
└────┴───────┴──────┘
┌─id─┬─value─┬─part─┐
│ 1 │ 1 │ 222 │
└────┴───────┴──────┘
功能效果通过上面的示例大家应该能够 get 到了吧?那么按照剧情的安排,接下来各位就会好奇是怎么实现的了吧?
我们都知道,ReplicatedMergeTree 是通过 ZooKeeper 记录了 block_id。那么 MergeTree 不依赖 ZooKeeper,靠什么记录呢?
也许你已经想到了,没错,是靠文件啊。进入到表的磁盘目录,我们会看到多出来了一个 deduplication_logs 文件目录:
代码语言:javascript复制total 8
drwxr-xr-x 11 nauu staff 352 8 14 11:42 111_1_3_1
drwxr-xr-x 11 nauu staff 352 8 14 11:44 222_4_4_0
drwxr-xr-x 3 nauu staff 96 8 14 11:28 deduplication_logs
drwxr-xr-x 2 nauu staff 64 8 14 11:28 detached
-rw-r----- 1 nauu staff 1 8 14 11:28 format_version.txt
进入目录,会发现 log 日志:
代码语言:javascript复制deduplication_log_1.txt
日志按行记录了分区名称和 block_id ,作为后续去重的判断依据:
代码语言:javascript复制cat deduplication_log_1.txt
1 111_1_1_0 111_5329300851986775609_11764731552300554311
1 111_3_3_0 111_16165894563003502484_10602898938383473825
1 222_4_4_0 222_15423518775339082225_1451687862585147197