- В любой момент времени в вашей таблице всё ещё могут быть дубликаты (строки с одинаковым ключом сортировки)
- Фактическое удаление дублирующихся строк происходит во время слияния частей
- Ваши запросы должны учитывать возможность наличия дубликатов
| ClickHouse предоставляет бесплатное обучение по дедупликации и многим другим темам. Модуль обучения по удалению и обновлению данных — отличная отправная точка. |
Варианты дедупликации
-
Движок таблицы
ReplacingMergeTree: в этом движке таблицы повторяющиеся строки с одинаковым ключом сортировки удаляются во время слияний.ReplacingMergeTree— хороший вариант для эмуляции поведения upsert (когда нужно, чтобы запросы возвращали последнюю вставленную строку). -
Схлопывание строк: движки таблиц
CollapsingMergeTreeиVersionedCollapsingMergeTreeиспользуют логику, при которой существующая строка «отменяется», а новая строка вставляется. Их сложнее реализовать, чемReplacingMergeTree, но запросы и агрегации при этом писать проще, поскольку не нужно учитывать, были ли данные уже слиты. Эти два движка таблиц полезны, когда данные нужно часто обновлять.
Использование ReplacingMergeTree для апсертов
views, вставьте новую строку с тем же первичным ключом (обратите внимание на новые значения в столбце views):
FINAL в запросе SELECT, что приведёт к логическому слиянию результата запроса:
Использовать
FINAL можно, если у вас небольшой объём данных. Если вы работаете с большим объёмом данных,
FINAL, вероятно, не лучший вариант. Давайте рассмотрим более подходящий способ
найти последнее значение столбца.Избегаем FINAL
views для обеих уникальных строк:
FINAL).
FINAL используем бизнес-логику: мы знаем, что столбец views всегда растёт, поэтому после группировки по нужным столбцам можно выбрать строку с наибольшим значением с помощью функции max:
FINAL.
В нашем модуле обучения по удалению и обновлению данных этот пример разбирается подробнее, в том числе показано, как использовать столбец version с ReplacingMergeTree.
Использование CollapsingMergeTree для частого обновления столбцов
ALTER TABLE..UPDATE, а просто вставить новые данные рядом с уже существующими. Можно было бы добавить столбец, который показывает, устарели данные или они актуальны… и для этого уже есть движок таблицы, который очень хорошо реализует такое поведение, тем более что он автоматически удаляет устаревшие данные. Давайте посмотрим, как это работает.
Предположим, мы отслеживаем количество просмотров комментария на Hacker News с помощью внешней системы и каждые несколько часов отправляем эти данные в ClickHouse. Мы хотим, чтобы старые строки удалялись, а новые отражали новое состояние каждого комментария на Hacker News. Для реализации такого поведения можно использовать CollapsingMergeTree.
Давайте определим таблицу для хранения количества просмотров:
hackernews_views есть столбец Int8 с именем sign, который называют столбцом sign. Имя столбца sign произвольно, но тип данных Int8 обязателен. Также обратите внимание, что имя столбца передаётся в конструктор таблицы CollapsingMergeTree.
Что представляет собой столбец sign в таблице CollapsingMergeTree? Он отражает состояние строки, и столбец sign может принимать только значения 1 или -1. Вот как это работает:
- Если две строки имеют одинаковый первичный ключ (или одинаковый порядок сортировки, если он отличается от первичного ключа), но разные значения столбца sign, то последняя вставленная строка со значением +1 становится строкой состояния, а остальные строки взаимно компенсируют друг друга
- Строки, которые взаимно компенсируют друг друга, удаляются во время слияний
- Строки, для которых не находится пары, сохраняются
hackernews_views. Поскольку это единственная строка для данного первичного ключа, мы устанавливаем её состояние в 1:
(123, 'ricardo'):
FINAL возвращается строка с текущим состоянием:
FINAL не рекомендуется для больших таблиц.
Значение, передаваемое в столбец
views в нашем примере, на самом деле не нужно и не обязано совпадать с текущим значением views в старой строке. Более того, вы можете отменить строку, указав только первичный ключ и -1:Обновления в реальном времени из нескольких потоков
CollapsingMergeTree строки взаимно отменяют друг друга с помощью столбца sign, а состояние строки определяется последней вставленной строкой. Но это может создавать проблемы, если строки вставляются из разных потоков и могут попадать в таблицу не по порядку. В такой ситуации опираться на «последнюю» строку не получится.
Здесь и пригодится VersionedCollapsingMergeTree — он выполняет схлопывание строк так же, как CollapsingMergeTree, но вместо последней вставленной строки сохраняет строку с наибольшим значением в указанном вами столбце версии.
Рассмотрим пример. Допустим, мы хотим отслеживать число просмотров наших комментариев на Hacker News, а данные часто обновляются. Нам нужно, чтобы отчётность использовала самые свежие значения без принудительного запуска слияний и без ожидания их завершения. Начнём с таблицы, похожей на CollapsedMergeTree, но добавим столбец для хранения версии состояния строки:
VersionsedCollapsingMergeTree, а также передаются столбец sign и столбец версии. Вот как работает эта таблица:
- Она удаляет каждую пару строк с одинаковыми первичным ключом и версией, но разными значениями sign
- Порядок, в котором были вставлены строки, не имеет значения
- Обратите внимание: если столбец версии не является частью первичного ключа, ClickHouse неявно добавляет его в первичный ключ последним полем
hackernews_views_vcmt:
VersionedCollapsingMergeTree весьма удобна, если нужно реализовать дедупликацию при вставке строк из нескольких клиентов и/или потоков.
Почему мои строки не дедуплицируются?
INSERT. Например, если вы вставляете строки со столбцом createdAt DateTime64(3) DEFAULT now(), они гарантированно будут уникальными, потому что для столбца createdAt у каждой строки будет своё уникальное значение по умолчанию. Движок таблицы MergeTree / ReplicatedMergeTree не сможет дедуплицировать такие строки, поскольку каждая вставленная строка будет иметь уникальную контрольную сумму.
В этом случае вы можете указать собственный insert_deduplication_token для каждого батча строк, чтобы повторные вставки одного и того же батча не приводили к повторной вставке тех же строк. Подробнее о том, как использовать эту настройку, см. в документации по insert_deduplication_token.