让MergeTree也具备ReplicatedMergeTree的数据去重能力

2021-08-25 15:36:25 浏览数 (1)

我曾在书中介绍过,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

0 人点赞