重试时插入操作的去重
由于诸如超时之类的错误,插入操作有时可能会失败。当插入失败时,数据可能已成功插入,也可能未成功插入。本指南介绍如何在插入重试时启用去重,以避免相同的数据被多次插入。
当重试插入时,ClickHouse 会尝试确定数据是否已成功插入。如果插入的数据被标记为重复,ClickHouse 将不会将其插入到目标表。但是,用户仍然会收到成功的操作状态,就好像数据已正常插入一样。
启用重试时插入操作的去重
表的插入去重
只有 *MergeTree
引擎支持插入去重。
对于 *ReplicatedMergeTree
引擎,默认情况下启用插入去重,并由 replicated_deduplication_window
和 replicated_deduplication_window_seconds
设置控制。对于非复制的 *MergeTree
引擎,去重由 non_replicated_deduplication_window
设置控制。
以上设置确定表的去重日志的参数。去重日志存储有限数量的 block_id
,这些 block_id
决定了去重的工作方式(见下文)。
查询级别的插入去重
设置 insert_deduplicate=1
在查询级别启用去重。请注意,如果您使用 insert_deduplicate=0
插入数据,即使您使用 insert_deduplicate=1
重试插入,该数据也无法去重。这是因为在使用 insert_deduplicate=0
插入期间,block_id
没有被写入块。
插入去重的工作原理
当数据插入到 ClickHouse 中时,它会根据行数和字节数将数据拆分为块。
对于使用 *MergeTree
引擎的表,每个块都分配一个唯一的 block_id
,它是该块中数据的哈希值。此 block_id
用作插入操作的唯一键。如果在去重日志中找到相同的 block_id
,则该块被视为重复项,并且不会插入到表中。
这种方法非常适用于插入包含不同数据的情况。但是,如果出于有意目的多次插入相同的数据,则需要使用 insert_deduplication_token
设置来控制去重过程。此设置允许您为每个插入指定一个唯一的令牌,ClickHouse 使用该令牌来确定数据是否重复。
对于 INSERT ... VALUES
查询,将插入的数据拆分为块是确定性的,并且由设置决定。因此,用户应使用与初始操作相同的设置值重试插入。
对于 INSERT ... SELECT
查询,重要的是查询的 SELECT
部分为每个操作返回相同的数据和相同的顺序。请注意,这在实际使用中很难实现。为了确保重试时数据顺序稳定,请在查询的 SELECT
部分中定义精确的 ORDER BY
部分。请记住,所选表可能会在重试之间更新:结果数据可能已更改,并且不会发生去重。此外,在您插入大量数据的情况下,插入后的块数可能会溢出到去重日志窗口,并且 ClickHouse 将无法识别要对块进行去重。
物化视图的插入去重
当表具有一个或多个物化视图时,插入的数据也会通过定义的转换插入到这些视图的目标中。转换后的数据也会在重试时进行去重。ClickHouse 对物化视图执行去重的方式与对插入到目标表的数据进行去重的方式相同。
您可以使用源表的以下设置来控制此过程
replicated_deduplication_window
replicated_deduplication_window_seconds
non_replicated_deduplication_window
您还可以使用用户配置文件设置 deduplicate_blocks_in_dependent_materialized_views
。
当将块插入到物化视图下的表时,ClickHouse 通过哈希一个字符串来计算 block_id
,该字符串组合了源表中的 block_id
和其他标识符。这确保了物化视图内的准确去重,允许根据数据的原始插入来区分数据,而无需考虑在到达物化视图下的目标表之前应用的任何转换。
示例
物化视图转换后相同的块
在物化视图内部转换期间生成的相同块不会去重,因为它们基于不同的插入数据。
这是一个示例
CREATE TABLE dst
(
`key` Int64,
`value` String
)
ENGINE = MergeTree
ORDER BY tuple()
SETTINGS non_replicated_deduplication_window=1000;
CREATE MATERIALIZED VIEW mv_dst
(
`key` Int64,
`value` String
)
ENGINE = MergeTree
ORDER BY tuple()
SETTINGS non_replicated_deduplication_window=1000
AS SELECT
0 AS key,
value AS value
FROM dst;
SET max_block_size=1;
SET min_insert_block_size_rows=0;
SET min_insert_block_size_bytes=0;
以上设置允许我们从一个表中选择一系列仅包含一行的块。这些小块不会被压缩,并且在插入到表中之前保持不变。
SET deduplicate_blocks_in_dependent_materialized_views=1;
我们需要在物化视图中启用去重
INSERT INTO dst SELECT
number + 1 AS key,
IF(key = 0, 'A', 'B') AS value
FROM numbers(2);
SELECT
*,
_part
FROM dst
ORDER by all;
┌─key─┬─value─┬─_part─────┐
│ 1 │ B │ all_0_0_0 │
│ 2 │ B │ all_1_1_0 │
└─────┴───────┴───────────┘
在这里我们看到两个 parts 已被插入到 dst
表中。来自 select 的 2 个块 -- 插入时的 2 个 parts。这些 parts 包含不同的数据。
SELECT
*,
_part
FROM mv_dst
ORDER by all;
┌─key─┬─value─┬─_part─────┐
│ 0 │ B │ all_0_0_0 │
│ 0 │ B │ all_1_1_0 │
└─────┴───────┴───────────┘
在这里我们看到 2 个 parts 已被插入到 mv_dst
表中。这些 parts 包含相同的数据,但是它们没有被去重。
INSERT INTO dst SELECT
number + 1 AS key,
IF(key = 0, 'A', 'B') AS value
FROM numbers(2);
SELECT
*,
_part
FROM dst
ORDER by all;
┌─key─┬─value─┬─_part─────┐
│ 1 │ B │ all_0_0_0 │
│ 2 │ B │ all_1_1_0 │
└─────┴───────┴───────────┘
SELECT
*,
_part
FROM mv_dst
ORDER by all;
┌─key─┬─value─┬─_part─────┐
│ 0 │ B │ all_0_0_0 │
│ 0 │ B │ all_1_1_0 │
└─────┴───────┴───────────┘
在这里我们看到,当我们重试插入时,所有数据都被去重了。去重对 dst
和 mv_dst
表都有效。
插入时相同的块
CREATE TABLE dst
(
`key` Int64,
`value` String
)
ENGINE = MergeTree
ORDER BY tuple()
SETTINGS non_replicated_deduplication_window=1000;
SET max_block_size=1;
SET min_insert_block_size_rows=0;
SET min_insert_block_size_bytes=0;
插入
INSERT INTO dst SELECT
0 AS key,
'A' AS value
FROM numbers(2);
SELECT
'from dst',
*,
_part
FROM dst
ORDER by all;
┌─'from dst'─┬─key─┬─value─┬─_part─────┐
│ from dst │ 0 │ A │ all_0_0_0 │
└────────────┴─────┴───────┴───────────┘
使用以上设置,select 产生两个块——因此,应该有两个块用于插入到表 dst
中。但是,我们看到只有一个块被插入到表 dst
中。发生这种情况是因为第二个块已被去重。它具有相同的数据和用于去重的键 block_id
,它是从插入的数据计算出的哈希值。这种行为不是预期的。这种情况很少发生,但在理论上是可能的。为了正确处理这种情况,用户必须提供 insert_deduplication_token
。让我们用以下示例来解决这个问题
使用 insert_deduplication_token
插入时相同的块
CREATE TABLE dst
(
`key` Int64,
`value` String
)
ENGINE = MergeTree
ORDER BY tuple()
SETTINGS non_replicated_deduplication_window=1000;
SET max_block_size=1;
SET min_insert_block_size_rows=0;
SET min_insert_block_size_bytes=0;
插入
INSERT INTO dst SELECT
0 AS key,
'A' AS value
FROM numbers(2)
SETTINGS insert_deduplication_token='some_user_token';
SELECT
'from dst',
*,
_part
FROM dst
ORDER by all;
┌─'from dst'─┬─key─┬─value─┬─_part─────┐
│ from dst │ 0 │ A │ all_2_2_0 │
│ from dst │ 0 │ A │ all_3_3_0 │
└────────────┴─────┴───────┴───────────┘
两个相同的块已按预期插入。
select 'second attempt';
INSERT INTO dst SELECT
0 AS key,
'A' AS value
FROM numbers(2)
SETTINGS insert_deduplication_token='some_user_token';
SELECT
'from dst',
*,
_part
FROM dst
ORDER by all;
┌─'from dst'─┬─key─┬─value─┬─_part─────┐
│ from dst │ 0 │ A │ all_2_2_0 │
│ from dst │ 0 │ A │ all_3_3_0 │
└────────────┴─────┴───────┴───────────┘
重试的插入操作已按预期去重。
select 'third attempt';
INSERT INTO dst SELECT
1 AS key,
'b' AS value
FROM numbers(2)
SETTINGS insert_deduplication_token='some_user_token';
SELECT
'from dst',
*,
_part
FROM dst
ORDER by all;
┌─'from dst'─┬─key─┬─value─┬─_part─────┐
│ from dst │ 0 │ A │ all_2_2_0 │
│ from dst │ 0 │ A │ all_3_3_0 │
└────────────┴─────┴───────┴───────────┘
即使该插入包含不同的插入数据,它也被去重了。请注意,insert_deduplication_token
具有更高的优先级:当提供 insert_deduplication_token
时,ClickHouse 不使用数据的哈希和。
不同的插入操作在物化视图的底层表中转换后生成相同的数据
CREATE TABLE dst
(
`key` Int64,
`value` String
)
ENGINE = MergeTree
ORDER BY tuple()
SETTINGS non_replicated_deduplication_window=1000;
CREATE MATERIALIZED VIEW mv_dst
(
`key` Int64,
`value` String
)
ENGINE = MergeTree
ORDER BY tuple()
SETTINGS non_replicated_deduplication_window=1000
AS SELECT
0 AS key,
value AS value
FROM dst;
SET deduplicate_blocks_in_dependent_materialized_views=1;
select 'first attempt';
INSERT INTO dst VALUES (1, 'A');
SELECT
'from dst',
*,
_part
FROM dst
ORDER by all;
┌─'from dst'─┬─key─┬─value─┬─_part─────┐
│ from dst │ 1 │ A │ all_0_0_0 │
└────────────┴─────┴───────┴───────────┘
SELECT
'from mv_dst',
*,
_part
FROM mv_dst
ORDER by all;
┌─'from mv_dst'─┬─key─┬─value─┬─_part─────┐
│ from mv_dst │ 0 │ A │ all_0_0_0 │
└───────────────┴─────┴───────┴───────────┘
select 'second attempt';
INSERT INTO dst VALUES (2, 'A');
SELECT
'from dst',
*,
_part
FROM dst
ORDER by all;
┌─'from dst'─┬─key─┬─value─┬─_part─────┐
│ from dst │ 1 │ A │ all_0_0_0 │
│ from dst │ 2 │ A │ all_1_1_0 │
└────────────┴─────┴───────┴───────────┘
SELECT
'from mv_dst',
*,
_part
FROM mv_dst
ORDER by all;
┌─'from mv_dst'─┬─key─┬─value─┬─_part─────┐
│ from mv_dst │ 0 │ A │ all_0_0_0 │
│ from mv_dst │ 0 │ A │ all_1_1_0 │
└───────────────┴─────┴───────┴───────────┘
我们每次插入不同的数据。但是,相同的数据被插入到 mv_dst
表中。数据未被去重,因为源数据不同。
具有等效数据的不同物化视图插入到同一底层表
CREATE TABLE dst
(
`key` Int64,
`value` String
)
ENGINE = MergeTree
ORDER BY tuple()
SETTINGS non_replicated_deduplication_window=1000;
CREATE TABLE mv_dst
(
`key` Int64,
`value` String
)
ENGINE = MergeTree
ORDER BY tuple()
SETTINGS non_replicated_deduplication_window=1000;
CREATE MATERIALIZED VIEW mv_first
TO mv_dst
AS SELECT
0 AS key,
value AS value
FROM dst;
CREATE MATERIALIZED VIEW mv_second
TO mv_dst
AS SELECT
0 AS key,
value AS value
FROM dst;
SET deduplicate_blocks_in_dependent_materialized_views=1;
select 'first attempt';
INSERT INTO dst VALUES (1, 'A');
SELECT
'from dst',
*,
_part
FROM dst
ORDER by all;
┌─'from dst'─┬─key─┬─value─┬─_part─────┐
│ from dst │ 1 │ A │ all_0_0_0 │
└────────────┴─────┴───────┴───────────┘
SELECT
'from mv_dst',
*,
_part
FROM mv_dst
ORDER by all;
┌─'from mv_dst'─┬─key─┬─value─┬─_part─────┐
│ from mv_dst │ 0 │ A │ all_0_0_0 │
│ from mv_dst │ 0 │ A │ all_1_1_0 │
└───────────────┴─────┴───────┴───────────┘
两个相等的块被插入到表 mv_dst
(如预期)。
select 'second attempt';
INSERT INTO dst VALUES (1, 'A');
SELECT
'from dst',
*,
_part
FROM dst
ORDER by all;
┌─'from dst'─┬─key─┬─value─┬─_part─────┐
│ from dst │ 1 │ A │ all_0_0_0 │
└────────────┴─────┴───────┴───────────┘
SELECT
'from mv_dst',
*,
_part
FROM mv_dst
ORDER by all;
┌─'from mv_dst'─┬─key─┬─value─┬─_part─────┐
│ from mv_dst │ 0 │ A │ all_0_0_0 │
│ from mv_dst │ 0 │ A │ all_1_1_0 │
└───────────────┴─────┴───────┴───────────┘
该重试操作在 dst
和 mv_dst
表上都被去重了。