跳至主要内容

重试时的去重插入

插入操作有时可能会由于超时等错误而失败。当插入失败时,数据可能已成功插入,也可能未成功插入。本指南介绍如何在插入重试时启用去重,以确保相同数据不会插入多次。

当插入操作重试时,ClickHouse 会尝试确定数据是否已成功插入。如果插入的数据被标记为重复,ClickHouse 不会将其插入目标表。但是,用户仍会收到一个成功的操作状态,就像数据已正常插入一样。

启用重试时的插入去重

表的插入去重

只有 *MergeTree 引擎支持插入时的去重。

对于 *ReplicatedMergeTree 引擎,插入去重默认启用,并由 replicated_deduplication_windowreplicated_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;

在这里我们看到两个部分已插入到 dst 表中。来自 select 的 2 个块 - 插入时的 2 个部分。这些部分包含不同的数据。

SELECT
*,
_part
FROM mv_dst
ORDER by all;

在这里我们看到两个部分已插入到 mv_dst 表中。这些部分包含相同的数据,但是它们没有去重。

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;

SELECT
*,
_part
FROM mv_dst
ORDER by all;

在这里我们看到,当我们重试插入时,所有数据都已去重。去重适用于 dstmv_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;

使用上述设置,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;

两个相同的块已按预期插入。

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;

重试插入已按预期去重。

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;

即使此插入包含不同的插入数据,它也会被去重。请注意,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;

SELECT
'from mv_dst',
*,
_part
FROM mv_dst
ORDER by all;

select 'second attempt';

INSERT INTO dst VALUES (2, 'A');

SELECT
'from dst',
*,
_part
FROM dst
ORDER by all;

SELECT
'from mv_dst',
*,
_part
FROM mv_dst
ORDER by all;

我们每次插入不同的数据。但是,相同的数据被插入到 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;

SELECT
'from mv_dst',
*,
_part
FROM mv_dst
ORDER by all;

两个相等块插入到 mv_dst 表中(如预期的那样)。

select 'second attempt';

INSERT INTO dst VALUES (1, 'A');

SELECT
'from dst',
*,
_part
FROM dst
ORDER by all;

SELECT
'from mv_dst',
*,
_part
FROM mv_dst
ORDER by all;

此重试操作在 dstmv_dst 表中均已去重。