Skip to content
ClickHouse Docs
ClickHouse DocsClickHouse Docs

Дедупликация вставок при повторных попытках

Операции вставки иногда могут завершаться с ошибками, например из-за тайм-аутов. При сбое вставки данные могли быть успешно вставлены, а могли и не быть. В этом руководстве рассказывается, как работает дедупликация при повторных попытках вставки, чтобы одни и те же данные не вставлялись больше одного раза.

При повторной попытке вставки ClickHouse пытается определить, были ли данные уже успешно вставлены. Если вставленные данные помечены как дубликат, ClickHouse не вставляет их в целевую таблицу. Однако пользователь всё равно получит статус успешного выполнения операции, как если бы данные были вставлены обычным образом.

Дедупликация охватывает синхронные вставки, асинхронные вставки и запросы INSERT ... SELECT. Один параметр, deduplicate_insert, управляет синхронными и асинхронными вставками. Для INSERT ... SELECT требуется особое внимание, и у него есть собственный параметр. См. Параметры, управляющие дедупликацией вставок.

Ограничения

Неопределённый статус вставки

Пользователь должен повторять операцию вставки, пока она не завершится успешно. Если все повторные попытки окажутся неудачными, определить, были ли данные вставлены, невозможно. Если задействованы materialized views, также неясно, в каких таблицах могли появиться данные. Materialized views могут быть рассинхронизированы с исходной таблицей.

Ограничение окна дедупликации

Если в ходе последовательности повторных попыток выполняется более *_deduplication_window других операций вставки, дедупликация может работать не так, как задумано. В этом случае одни и те же данные могут быть вставлены несколько раз.

Настройки, управляющие дедупликацией вставок

ClickHouse выполняет дедупликацию вставки только при соблюдении обоих следующих условий:

  1. Целевая таблица хранит журнал дедупликации. Это настройка уровня таблицы.
  2. Для запроса включена дедупликация. Это настройка уровня запроса.

Настройки уровня таблицы

Только движки *MergeTree поддерживают дедупликацию при вставке.

Для движков *ReplicatedMergeTree журнал дедупликации включен по умолчанию и управляется настройками replicated_deduplication_window и replicated_deduplication_window_seconds. Для нереплицируемых движков *MergeTree журнал управляется настройкой non_replicated_deduplication_window, которая по умолчанию имеет значение 0. Поэтому обычная таблица MergeTree не выполняет дедупликацию, пока вы не зададите для этого окна положительное значение.

Перечисленные выше настройки определяют параметры журнала дедупликации таблицы. Журнал дедупликации хранит конечное число block_id, которые определяют, как работает дедупликация (см. ниже).

Настройки на уровне запроса

Настройка Применяется к Значение по умолчанию Назначение
deduplicate_insert Каждому INSERT, синхронному или асинхронному enable Основной переключатель дедупликации вставок
deduplicate_insert_select INSERT ... SELECT enable_when_possible Определяет, что делать, если результат SELECT невозможно воспроизвести
insert_deduplication_token Каждому INSERT '' Идентифицирует вставку по заданной пользователем строке, а не по данным
deduplicate_blocks_in_dependent_materialized_views Таблицам, лежащим в основе materialized view 1 Распространяет дедупликацию на целевые таблицы зависимых materialized view

deduplicate_insert принимает три значения:

  • enable — дедупликация включена для запроса INSERT.
  • disable — дедупликация отключена для запроса INSERT.
  • backward_compatible_choice — решение передаётся устаревшим настройкам insert_deduplicate (синхронные вставки) и async_insert_deduplicate (асинхронные вставки).

Обратите внимание: запрос, выполняемый с deduplicate_insert = disable, не записывает block_id для своих блоков. Такие данные нельзя дедуплицировать позднее, даже если повторить вставку с deduplicate_insert = enable. То же относится к случаям, когда целевая таблица не хранит журнал дедупликации: ничего не записывается, поэтому при повторной попытке сопоставить данные будет не с чем.

Старшинство

  1. Для запроса INSERT ... SELECT определяющим является параметр deduplicate_insert_select. См. Дедупликация для INSERT … SELECT.
  2. Для всех остальных операций INSERT определяющим является параметр deduplicate_insert.
  3. Параметры insert_deduplicate и async_insert_deduplicate учитываются только при значении backward_compatible_choice параметра deduplicate_insert.

Устаревшие и упразднённые настройки

Настройка Статус Используйте вместо
insert_deduplicate Устаревшая. Считывается только при deduplicate_insert = backward_compatible_choice deduplicate_insert
async_insert_deduplicate Устаревшая. Считывается только при deduplicate_insert = backward_compatible_choice deduplicate_insert
insert_select_deduplicate Упразднённая. Не влияет на работу deduplicate_insert_select
update_insert_deduplication_token_in_dependent_materialized_views Упразднённая. Не влияет на работу

В версии 26.2 также были изменены значения по умолчанию для async_insert и deduplicate_blocks_in_dependent_materialized_views: теперь они включены. Настройка compatibility управляет всеми тремя настройками. Если установить для compatibility версию ранее 26.2, эти настройки сохранят прежние значения по умолчанию: для deduplicate_insert будет установлено backward_compatible_choice, и выбор будет передан insert_deduplicate и async_insert_deduplicate. Явно заданная настройка всегда применяется и не зависит от compatibility.

Как работает дедупликация при вставке

Когда данные вставляются в ClickHouse, система разбивает их на блоки в зависимости от количества строк и байтов.

Для таблиц, использующих движки *MergeTree, каждому блоку присваивается уникальный block_id — хеш данных в этом блоке. Этот block_id используется как уникальный ключ операции вставки. Если такой же block_id найден в журнале дедупликации, блок считается дубликатом и не вставляется в таблицу.

Этот подход хорошо работает, когда вставки содержат разные данные. Однако если одни и те же данные намеренно вставляются несколько раз, нужно использовать настройку insert_deduplication_token, чтобы управлять процессом дедупликации. Эта настройка позволяет указать уникальный токен для каждой вставки, который ClickHouse использует для определения, являются ли данные дубликатом. insert_deduplication_token имеет более высокий приоритет: если указан токен, ClickHouse не использует хеш-сумму данных.

Для запросов INSERT ... VALUES разбиение вставляемых данных на блоки детерминировано и задаётся настройками. Поэтому повторные попытки вставки следует выполнять с теми же значениями настроек, что и в исходной операции.

Дедупликация для INSERT ... SELECT

Для запросов INSERT ... SELECT часть SELECT должна при каждой попытке возвращать одни и те же данные в одном и том же порядке. В противном случае блоки и их block_id будут различаться, поэтому повторная попытка не будет распознана как дубликат.

ClickHouse не может проверить, что исходные данные не изменились, но может определить, даёт ли сам запрос воспроизводимый результат. SELECT считается стабильным, если выполняются оба следующих условия:

  • Запрос содержит предложение ORDER BY ALL. Распознаётся только точная конструкция ORDER BY ALL. Обычный ORDER BY <expressions> не распознаётся, а UNION из двух или более SELECT никогда не считается стабильным.
  • Конвейер чтения завершается одним потоком.

Непустой insert_deduplication_token — равноценная замена стабильности, поскольку в этом случае вставку идентифицирует токен, а не данные.

Настройка deduplicate_insert_select определяет поведение:

Значение Поведение
enable_when_possible (по умолчанию) Выполнять дедупликацию, если SELECT стабилен или задан токен. В противном случае пропустить дедупликацию и записать сообщение в журнал сервера.
force_enable Всегда выполнять дедупликацию. Если SELECT нестабилен и токен не задан, сгенерировать исключение DEDUPLICATION_IS_NOT_POSSIBLE.
enable_even_for_bad_queries Выполнять дедупликацию независимо от стабильности. Сохранено для обратной совместимости. При нестабильном SELECT повторная попытка обычно не распознаётся как дубликат, поэтому предпочтительнее выбрать другое значение.
disable Никогда не выполнять дедупликацию INSERT ... SELECT.

enable_when_possible и enable_even_for_bad_queries также учитывают deduplicate_insert: если оно имеет значение disable, запрос не дедуплицируется. force_enable переопределяет deduplicate_insert.

Помните, что выбранная таблица может обновиться между повторными попытками. В этом случае два подхода дают противоположный результат:

  • Без insert_deduplication_token значения block_id вычисляются на основе данных. Изменённый результат создаёт другие block_id, дедупликация не выполняется, и повторная попытка вставляет новые данные поверх всего, что уже было записано при первой попытке.
  • С insert_deduplication_token вставку идентифицирует только токен. Повторная попытка распознаётся как дубликат и отбрасывается, даже если она вставила бы другие данные.

Выберите подход в соответствии с тем, какой смысл должна иметь повторная попытка. Кроме того, при вставке больших объёмов данных количество блоков может превысить размер окна журнала дедупликации, и ClickHouse не сможет дедуплицировать эти блоки.

Дедупликация асинхронных вставок

Асинхронные вставки (async_insert, включенные по умолчанию с версии 26.2) дедуплицируются при повторных попытках так же, как синхронные вставки. Обоими типами управляет параметр deduplicate_insert, поэтому отдельный переключатель не нужен.

Оба типа вставок также используют общий журнал дедупликации и одинаково вычисляют block_id. Поэтому можно переключать клиент между синхронными и асинхронными вставками, не нарушая дедупликацию: повторная попытка, отправленная в одном режиме, всё равно будет распознана как дубликат попытки, отправленной в другом. Перевод рабочей нагрузки с синхронных на асинхронные вставки также безопасен для таблицы, использующей дедупликацию.

Гранулярность дедупликации

Сервер объединяет несколько асинхронных вставок в один батч и записывает его в виде одной или нескольких частей — как минимум по одной для каждого уникального значения ключа партиционирования. Дедупликация выполняется для каждого пользовательского запроса, а не для батча:

  • Каждый запрос в очереди добавляет в батч один токен дедупликации.
  • Токен — это либо значение insert_deduplication_token, если оно задано в запросе, либо хеш строк, добавленных этим запросом.
  • Объединение в батчи не влияет на токены, а insert_deduplication_token не влияет на группировку запросов в батчи.

Это приводит к двум последствиям:

  • Если один из запросов в батче является дубликатом, ClickHouse удаляет только строки этого запроса. Остальные данные из батча вставляются как обычно. Часть полностью пропускается только в том случае, если из неё удалены все строки.
  • Если два запроса в одном батче имеют одинаковый токен, второй отбрасывается до записи части. Это применяется отдельно для каждой партиции: если два запроса записывают строки в разные партиции, оба сохраняются.

События DuplicatedAsyncInserts и SelfDuplicatedAsyncInserts в system.events учитывают эти два случая.

Асинхронные вставки и materialized view

Дедупликация асинхронных вставок работает совместно с зависимыми materialized view. Правило простое: один блок на входе — один блок на выходе. Если внутренний запрос представления преобразует один входной блок в один выходной, дедупликация работает. Если представление формирует второй блок, ClickHouse генерирует исключение NOT_IMPLEMENTED.

Представление формирует второй блок, когда его результат уже не помещается в один блок. max_block_size задаёт количество строк, помещающихся в блок. Преобразования столбцов, фильтрация и агрегация никогда не добавляют строк, поэтому всегда остаются в одном блоке. JOIN может добавлять строки. Он работает, пока результат не превышает max_block_size, и завершается ошибкой при превышении этого значения.

Чтобы выполнять вставку через представление, формирующее более одного блока, либо установите deduplicate_blocks_in_dependent_materialized_views = 0, либо используйте синхронные вставки.

Дедупликация при вставке с materialized view

Если у таблицы есть одно или несколько materialized view, вставляемые данные также записываются в целевые таблицы этих представлений с заданными преобразованиями. Преобразованные данные тоже дедуплицируются при повторных попытках. ClickHouse выполняет дедупликацию для materialized view так же, как и для данных, вставляемых в целевую таблицу.

Управлять этим процессом можно с помощью следующих настроек исходной таблицы:

Дедупликация в таблицах materialized view дополнительно регулируется настройкой профиля пользователя deduplicate_blocks_in_dependent_materialized_views, которая включена по умолчанию начиная с версии 26.2. Для дедупликации должны быть включены оба параметра: deduplicate_insert дедуплицирует данные, вставляемые в исходную таблицу, а deduplicate_blocks_in_dependent_materialized_views дополнительно дедуплицирует данные в зависимых таблицах. Для полной дедупликации включите оба параметра.

При вставке блоков в таблицы materialized view ClickHouse вычисляет block_id, хешируя строку, которая объединяет block_id исходной таблицы и дополнительные идентификаторы. Это обеспечивает точную дедупликацию в materialized view и позволяет различать данные по их исходной вставке независимо от преобразований, применённых до записи в целевую таблицу materialized view.

Примеры

Идентичные блоки после преобразований в materialized view

Идентичные блоки, сгенерированные при преобразовании внутри materialized view, не дедуплицируются, поскольку они основаны на разных вставленных данных.

Вот пример:

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;

Приведенные выше настройки позволяют выбирать данные из таблицы, состоящей из последовательности блоков, каждый из которых содержит только одну строку. Эти небольшие блоки не объединяются и остаются неизменными, пока не будут вставлены в таблицу.

Мы явно задаем дедупликацию в materialized view, хотя по умолчанию она включена:

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

Здесь видно, что в таблицу mv_dst было вставлено 2 части. Эти части содержат одни и те же данные, однако дедупликация для них не выполнялась.

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 не использует хеш-сумму данных.

Разные операции вставки после преобразования создают одинаковые данные в базовой таблице materialized view

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 вставляются одни и те же данные. Данные не дедуплицируются, потому что исходные данные различались.

Разные варианты вставки через materialized view в одну базовую таблицу с эквивалентными данными

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.

Navigation