Эту статью можно написать очень кратко - просто отключите вообще все, что связано с дедупликацией (список настроек будет дан), и научитесь жить с дублями (проще сказать, чем сделать). Но, для начала нужно раскрыть корень проблемы и "расставить точки над И" - дилеммой о том, теряет ли ClickHouse данные или нет. Для нетерпеливых сразу же скажу вывод - да, МОЖЕТ терять, но не факт, что вы с этим столкнетесь. А если и столкнулись - то это легко исправимо (а вот догадаться до этого отнюдь). Информацию из статьи ОБЯЗАТЕЛЬНО нужно учитывать при конфигурировании вашего аналитического хранилища. Поехали.
P.S. читать каждую ссылку из статьи совсем не обязательно, основные тейки из ссылок изложены в статье.
1. Введение
Представим, что вы прочитали статьи/бенчмарки/официальную документацию о невероятной (и это правда) скорости выполнения аналитических запросов ClickHouse. Вы прочитали про разреженный первичный индекс и о том, что он вообще не про то, чтобы гарантировать отсутствие дублей (в них вся соль и суть), в отличие от классических СУБД. Также вы в курсе, что в ClickHouse из ACID только гарантии на INSERT (с большим количеством оговорок и условий, как при получении кредита, но все же).
Вы принимаете единственное (на мой взгляд, да и самих разработчиков ClickHouse тоже) логичное решение - раз есть гарантии только на INSERT, значит можно выстраивать аналитическое хранилище данных по принципу append-only (кратко - только INSERT в совокупности с версионированием данных, никаких UPDATE/DELETE и прочих OLTP операций). Не совсем удобно, но звучит как решение, чтобы гарантировать хотя бы буковку A (атомарность) аббревиатуры ACID. Отсюда делаем логичный вывод - мы ведь не будем терять данные при INSERT, так ведь? - Предполагаем, что так.
Вы поднимаете реплицированный кластер. Причем, в рамках статьи и понимания проблемы неважно, шардированный кластер или нет. Главное, чтобы была хотя бы одна реплика какой-либо ноды, важен сам факт наличия координатора (ZooKeeper).
Теперь вы начинаете работу с вашим хранилищем. Вы хотите реализовать пайплайн с материализованным представлением. Вот отличная статья для знакомства с мат вьюхами, либо же официальная документация. Кратко, мат вьюхи в ClickHouse - это механизм захвата вставляемых в таблицу данных (некий аналог CDC, но только на INSERT, возвращаемся к концепции append-only) с последующей обработкой этих данных (например, можно на лету их агрегировать и посчитать какие-либо метрики) и материализацией в какую-либо таблицу. Представим, что вы реализовали самый простой пайплайн без преобразований данных в стиле исходная таблица - мат вьюха - конечная таблица
. Выглядит примерно так:
CREATE TABLE source_table (
message String
) engine=MergeTree()
ORDER BY (message)
;
CREATE TABLE final_table (
message String
) engine=MergeTree()
ORDER BY (message)
;
CREATE MATERIALIZED VIEW mv_source_table TO final_table AS
SELECT
message
FROM source_table
;
Что же этот пайплайн делает? - просто переливает новые данные (колонку message) из source_table в final_table. Без каких-либо преобразований. Неважно, как данные попадают в source_table, неважно что это за данные. Важно другое - при организации такого пайплайна мы ожидаем, что final_table будет полностью идентична source_table. Но, по истечение некоторого времени (абсолютный рандом, не зависящий ни от количества данных, ни от времени суток, ни от самих данных, ни от фазы луны) вы с удивлением обнаруживаете, что в конечной таблице final_table данных не хватает (либо же, что мат вьюха, которая на лету агрегирует, считает какие-то неправильные метрики). Вы начинаете копать и понимаете, что часть данных была утеряна или проигнорирована мат вьюхой. Пытаетесь найти в упущенных данных логику (тип данных, время вставки, количество упущенных данных и т.д.) - и не находите ничего общего между утерянными данными. Как же это могло произойти? - ответ в дедупликации + в том, как физически происходит INSERT в ClickHouse.
2. А INSERT точно атомарен?
На данном этапе немножко абстрагируемся от материализованных представлений. Мы к ним вернемся, но это крайне важно сделать в конце статьи, дабы расставить точки над И. Пока что просто запомним, что у нас есть проблемная мат вьюха и, как утверждает автор данной статьи, проблема в комбинации дедупликации + и особенностей INSERT. Давайте распутывать. Что за механизм "дедупликация" можно (но не нужно, во имя вашей психики) почитать в официальной документации - Дедупликация вставок при повторных попытках | ClickHouse Docs. Постараюсь описать кратко и простым языком - есть у ClickHouse под капотом такой механизм, как дедупликация. Нужен он для того, чтобы при ПОВТОРНЫХ вставках данных в таблицу дубли автоматически удалялись. Например, произошла какая-либо подкапотная (это важно, так как гадость кроется именно в ПОВТОРНОЙ попытке вставки) ошибка во время выполнения обычной операции INSERT INTO t1 select * from t2
. В указанной выше статье описано, как физически происходит INSERT, вот вырезка - "когда данные вставляются в ClickHouse, они разбиваются на блоки в зависимости от количества строк и байтов". И вот тут сразу же возникает ряд важных вопросов - возможна ли ситуация, когда часть блоков данных вставилась, а часть нет? Что делает ClickHouse под капотом, если какой-то блок не получилось вставить? Как обстоят дела с обещанной атомарностью INSERT? Ответ краток - да, действительно возможна ситуация, при которой из 10 блоков данных будут записаны лишь 5. И, ClickHouse попытается ПОВТОРНО записать недостающие 5 блоков еще раз, не уведомляя нас о том, что что-то пошло не по плану (для пользователя это вообще никак не будет отражено, все будет как при обычном INSERT). И, как не трудно догадаться, если в момент повторной вставки ClickHouse упадет - то так и останется вставленными лишь 5 блоков данных из 10.
Но как же так получается, ранее мы ведь читали, что разработчиками гарантируется атомарность INSERT? Очень просто, ранее было оговорено про количество условий ACID, как при получении кредита. Вырезка из официальной документации - "Случай 1: INSERT в одну партицию одной таблицы семейства MergeTree*. Это транзакционно (ACID), если вставленные строки упаковываются и вставляются как единый блок".
Добро пожаловать в суровый мир, где нужно внимательно читать тот самый мелкий шрифт внизу страницы. Гарантируется атомарность вставки БЛОКА данных в одну партицию, что даже приблизительно может быть не равно всем данным, присутствующим в INSERT. Все содержимое вставляемых данных может быть разбито под капотом самим ClickHouse на сколько угодно блоков, в зависимости от большого количества условий и настроек. Например, на это влияет настройка max_insert_block_size - максимальный размер блока (вставляете терабайт - держите 100500 блоков, из которых МОЖЕТ вставится лишь половина). А еще нужно, чтобы данные вставлялись строго в одну партицию. А еще нужно, чтобы не было параллельных вставок в ту же партицию. И это не все условия, которые мне удалось раскопать в процессе изучения проблемы.
3. insert_deduplicate
И так, мы поняли, что целиком INSERT не атомарен, атомарна лишь вставка блоков данных. Также мы помним, что есть еще какая-то дедупликация, которая под капотом во время операции INSERT сама удаляет дубли. Как же она это делает, и что считает дублем? Ответ конкретно в этой части документации. Вот вырезка оттуда - "Для таблиц, использующих движки *MergeTree
, каждому блоку присваивается уникальный block_id
, который является хешем данных в этом блоке. Этот block_id
используется в качестве уникального ключа для операции вставки. Если тот же block_id
найден в журнале дедупликации, блок считается дубликатом и не вставляется в таблицу." И так, вот мы и соединили дедупликацию и особенности INSERT (дедуплицируются блоки данных целиком). Нужное выделил жирным. А теперь загадка от Жака Фреско - не будет ли такой ситуации, когда еще НЕ записанные в таблицу блоки данных посчитаются механизмом дедупликации дублем и не запишутся в таблицу? Думаю, ответ на этот вопрос дадут сами разработчики ClickHouse самим фактом наличия серверной настройки insert_deduplicate, позволяющей отключить дедупликацию блоков данных (для реплицируемых* таблиц). Реплицируемых выделил жирным, и ранее в статье говорилось о том, что нужен именно реплицируемый кластер (на НЕ реплицированном дедупликация отключена - настройка non_replicated_deduplication_window по умолчанию 0). Так вот, если дедупликация - это идеальный механизм, в котором прекрасно и без ошибок гармонируют max_insert_block_size, партиции, репликация, конкурентные вставки и, наверняка, что-то еще, то зачем нужна настройка, отключающая его? Думаю, ответ очевиден. Первое, что делаем на пути к отсутствию потерь данных - insert_deduplicate = 0
.
Так как я не единственный, кто сталкивался с подобной проблемой, оставлю ссылку на боль коллеги, столкнувшимся с подобным. Его решение было таким же - insert_deduplicate = 0. Помимо этого, в его статье есть такая фраза - "Мне не приходилось наблюдать такой же проблемы с использованием MATERIALIZED VIEW
, которые в Clickhouse
позволяют делать автоматическую вставку на постоянной основе". Но, это же наш с вами кейс! Поехали, дальше самый смак.
P.S. Коллега сверху не единственный, кто столкнулся с тем, что ClickHouse данные теряет. Ребята из туту.ру вообще целый комплекс мероприятий устроили по поиску потерянных данных - Потери данных при репликации в аналитическое хранилище — автоматические сверки и мониторинг качества данных / Хабр. Я понимаю и разделяю их боль.
4. deduplicate_blocks_in_dependent_materialized_views
Теперь приступаем к решению нашего кейса - мат вьюха теряет данные. Долго тянуть не буду, за это отвечает настройка deduplicate_blocks_in_dependent_materialized_views. Что делает мат вьюха под капотом? - Все тот же злосчастный INSERT в конечную таблицу. А значит, ее работа также подвержена дедупликации со всем ее зоопарком условий. Но, не все так просто. Для мат вьюх есть специальная настройка дедупликации. Кратко, ее суть - по умолчанию дедупликация осуществляется не на конечной таблице, а на таблице-источнике, и, результат проверок на наличие дублей переносится на мат вьюху. Но, сделав deduplicate_blocks_in_dependent_materialized_views = 1
мы изменим это поведение. И, именно это и нужно сделать. Переписывать полностью текст из официальной документации не вижу смысла, прочитайте сами. Сделаю интереснее.
Пора "расставить точки над И" в дилемме о потере данных. Оставлю скрин из официальной документации, дабы сохранить этот факт навсегда.

Думаю, на человеческий переводить не нужно, но все же: не получит второй вставки = потеряет данные. Если уж сами разработчики в официальной документации предупреждают, что ClickHouse в базовой конфигурации имеет механизм для того, чтобы терять данные, то кто мы такие, чтобы с ними спорить?
5. Заключение
Могу лишь предположить, почему дедупликация по умолчанию активирована, но не буду - не вижу смысла уходить в догадки. Главное, что необходимо вынести из статьи:
Автор большой фанат ClickHouse и хочет, что ClickHouse не только не тормозил, но и не терял данные.
ClickHouse умеет НЕ терять данные, но, по какой-то причине, по умолчанию сконфигурирован так, что готов терять (но не факт, что вы вообще столкнетесь с потерями данных, рулетка в чистом виде). Это проблема больше идеологическая, на мой взгляд, нежели техническая, так как легко решается.
Проще выучить новый язык программирования, чем понять, как работает дедупликация в ClickHouse.
insert_deduplicate = 0
- не теряем данные из-за дедупликации при вставках в таблицу.deduplicate_blocks_in_dependent_materialized_views = 1
- не теряем данные из-за дедупликации при вставках в конечную таблицу при работе мат вьюх.Учимся выстраивать работу с ClickHouse, учитывая, что дубли есть всегда и везде. В этом поможет, например, ReplacingMergeTree | ClickHouse Docs.
Пользуйтесь на здоровье!
Комментарии (4)
youlose
31.07.2025 05:57В доке про deduplicate_blocks_in_dependent_materialized_views (и судя по доке она наоборот должна работать) речь идёт про Replicated* (например, ReplicatedMergeTree). Вы уверены что в том виде что вы указали эта проблема есть? Ибо тогда это выглядит как баг.
Razoon
31.07.2025 05:57Так вроде с самого начала было указано что речь идёт про реплицированные таблицы.
Ravius
Из статьи не все понял.
Если делать вставку message в мат view с индексом (message) - то она схлопнется - это описывается как потери данных?
Воспроизведется ли проблема, если у нас в orderby указаны (message, message_id)?
select_zvezdo4ka_from Автор
1) Схлопывание реальных дублей движком ReplacingMergeTree по тому ключу, который в секции order by - это правильная работа, специально нацеленная на дедупликацию. Сама по себе мат вьюха в клике ничего не схлопывает, все зависит от движка таблицы под капотом.
2) Я так и не сумел наиграть реальный воспроизводимый кейс. Ловил на проде потери данных мат вьюхами (а вот таблицами - нет), много экспериментировал, но сумел лишь исправить проблему, и описать причины (дедупликация + особенности вставки + еще куча окружающих вещей), по которым данные могут теряться.
3) Если же вы столкнулись с реальной потерей данных мат вьюхой - понаблюдайте за ней пару дней, соберите немного статистики (как часто теряет, сколько и т.д.). Затем добавьте в config.xml настройку deduplicate_blocks_in_dependent_materialized_views = 1. Ну и наблюдайте, что потери прекратились.