跳至主要内容
跳至主要内容
编辑此页

重试时的去重插入

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

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

限制

不确定的插入状态

用户必须重试插入操作,直到成功为止。如果所有重试都失败,则无法确定数据是否已插入。当涉及物化视图时,数据可能出现在哪些表中也尚不清楚。物化视图可能与源表不同步。

去重窗口限制

如果在重试序列期间发生超过 *_deduplication_window 个其他插入操作,去重可能无法按预期工作。在这种情况下,相同的数据可能会被插入多次。

启用重试时的插入去重

表的插入去重

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

对于 *ReplicatedMergeTree 引擎,插入去重默认启用,并由 replicated_deduplication_windowreplicated_deduplication_window_seconds 设置控制。对于非复制的 *MergeTree 引擎,去重由 non_replicated_deduplication_window 设置控制。

上述设置确定表的去重日志的参数。去重日志存储有限数量的 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 ALL 部分。目前,您必须在查询中使用完全的 ORDER BY ALL。尚不支持 ORDER BY,并且查询的 SELECT 部分将不被视为稳定。请记住,在重试之间,所选表可能会被更新 - 结果数据可能会发生变化,并且不会发生去重。此外,在插入大量数据的情况下,插入后的块数可能会超出去重日志窗口,并且 ClickHouse 将不知道如何去重这些块。目前,INSERT ... SELECT 的行为由 insert_select_deduplicate 设置控制。此设置确定是否将去重应用于使用 INSERT ... SELECT 查询插入的数据。有关详细信息和用法示例,请参阅链接的文档。

具有物化视图的插入去重

当表具有一个或多个物化视图时,插入的数据也会根据定义的转换插入到这些视图的目标位置。转换后的数据也会在重试时进行去重。ClickHouse 以与去重插入到目标表相同的方式对物化视图执行去重。

您可以使用以下设置来控制源表的此过程

您还必须启用用户配置文件设置 deduplicate_blocks_in_dependent_materialized_views。启用设置 insert_deduplicate=1 后,插入的数据将在源表中进行去重。设置 deduplicate_blocks_in_dependent_materialized_views=1 还会启用对依赖表中进行去重。如果需要完全去重,则必须同时启用这两个设置。

在将块插入到物化视图下的表时,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 │
└─────┴───────┴───────────┘

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

SELECT
    *,
    _part
FROM mv_dst
ORDER BY all;

┌─key─┬─value─┬─_part─────┐
│   0 │ B     │ all_0_0_0 │
│   0 │ B     │ all_1_1_0 │
└─────┴───────┴───────────┘

我们看到 2 个部分被插入到 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;

┌─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 │
└─────┴───────┴───────────┘

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

┌─'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 │
└───────────────┴─────┴───────┴───────────┘

该重试操作在表 dstmv_dst 上都被去重。

    © . This site is unofficial and not affiliated with ClickHouse, Inc.