Стратегии дедупликации (с использованием CDC)
Операции обновления и удаления, реплицируемые из Postgres в ClickHouse, приводят к дублированию строк в ClickHouse из‑за его структуры хранения данных и процесса репликации. На этой странице объясняется, почему это происходит, и описываются стратегии работы с дубликатами в ClickHouse.
Как происходит репликация данных?
Логическое декодирование PostgreSQL
ClickPipes использует механизм Postgres Logical Decoding, чтобы считывать изменения по мере их появления в Postgres. Процесс Logical Decoding в Postgres позволяет таким клиентам, как ClickPipes, получать изменения в человекочитаемом формате, то есть в виде последовательности операторов INSERT, UPDATE и DELETE.
ReplacingMergeTree
ClickPipes сопоставляет таблицы Postgres с ClickHouse, используя движок ReplacingMergeTree. ClickHouse лучше всего работает с нагрузками только на добавление данных (append-only) и не рекомендует частые операции UPDATE. В таких сценариях ReplacingMergeTree особенно эффективен.
С ReplacingMergeTree обновления моделируются как вставки новой версии (_peerdb_version) строки, а удаления — как вставки с более новой версией и флагом _peerdb_is_deleted, установленным в true. Движок ReplacingMergeTree в фоновом режиме устраняет дубликаты и объединяет данные, сохраняя последнюю версию строки для заданного первичного ключа (id), что позволяет эффективно обрабатывать операции UPDATE и DELETE как версионные вставки.
Ниже приведён пример оператора CREATE TABLE, выполняемого ClickPipes для создания таблицы в ClickHouse.
Показательный пример
Иллюстрация ниже пошагово показывает базовый пример синхронизации таблицы users между PostgreSQL и ClickHouse с использованием ClickPipes.

Шаг 1 показывает начальный снимок двух строк в PostgreSQL и то, как ClickPipes выполняет первоначальную загрузку этих двух строк в ClickHouse. Как можно увидеть, обе строки копируются в ClickHouse как есть.
Шаг 2 показывает три операции над таблицей users: вставку новой строки, обновление существующей строки и удаление другой строки.
Шаг 3 показывает, как ClickPipes реплицирует операции INSERT, UPDATE и DELETE в ClickHouse в виде версионированных вставок. Операция UPDATE отображается как новая версия строки с ID 2, а операция DELETE — как новая версия ID 1, помеченная как true с помощью _is_deleted. Из-за этого в ClickHouse на три строки больше по сравнению с PostgreSQL.
В результате выполнение простого запроса вроде SELECT count(*) FROM users; может дать разные результаты в ClickHouse и PostgreSQL. Согласно документации по слияниям в ClickHouse, устаревшие версии строк в конечном итоге отбрасываются в процессе слияния. Однако момент выполнения этого слияния непредсказуем, поэтому запросы в ClickHouse могут возвращать несогласованные результаты до тех пор, пока оно не произойдет.
Как обеспечить идентичные результаты запросов в ClickHouse и PostgreSQL?
Удаление дубликатов с помощью ключевого слова FINAL
Рекомендуемый способ удаления дубликатов данных в запросах ClickHouse — использовать модификатор FINAL. Он гарантирует, что будут возвращены только строки без дубликатов.
Рассмотрим, как применить его к трём разным запросам.
Обратите внимание на предикат WHERE в следующих запросах, который используется для фильтрации удалённых строк.
- Простой запрос с COUNT: Посчитать количество постов.
Это самый простой запрос, который можно выполнить, чтобы проверить, что синхронизация прошла успешно. Оба запроса должны вернуть одно и то же количество.
- Простая агрегация с JOIN: топ‑10 пользователей, которые накопили больше всего просмотров.
Пример агрегации по одной таблице. Наличие дубликатов здесь существенно повлияло бы на результат функции sum.
Параметр FINAL
Вместо того чтобы добавлять модификатор FINAL к каждому имени таблицы в запросе, вы можете использовать параметр FINAL, чтобы он автоматически применялся ко всем таблицам в запросе.
Этот параметр можно задать как для отдельного запроса, так и для всего сеанса.
Политика ROW
Простой способ скрыть избыточный фильтр _peerdb_is_deleted = 0 — использовать политику ROW. Ниже приведён пример, который создаёт политику ROW для исключения удалённых строк во всех запросах к таблице votes.
Политики на уровне строк применяются к списку пользователей и ролей. В этом примере они применяются ко всем пользователям и ролям. Их можно настроить так, чтобы они применялись только к конкретным пользователям или ролям.
Запросы как в Postgres
Миграция аналитического набора данных из PostgreSQL в ClickHouse часто требует изменения запросов приложения с учётом различий в обработке данных и выполнении запросов.
В этом разделе рассматриваются подходы к дедупликации данных, позволяющие сохранять исходные запросы без изменений.
Представления
Представления — отличный способ скрыть ключевое слово FINAL в запросе, так как они не хранят данные и при каждом обращении просто читают данные из другой таблицы.
Ниже приведён пример создания представлений для каждой таблицы в нашей базе данных ClickHouse с использованием ключевого слова FINAL и фильтрации удалённых строк.
После этого мы можем выполнять запросы к представлениям, используя тот же самый запрос, что и в PostgreSQL.
Refreshable materialized view
Другой подход — использовать refreshable materialized view, который позволяет по расписанию выполнять запрос для дедупликации строк и сохранения результатов в целевой таблице. При каждом обновлении по расписанию целевая таблица заменяется последними результатами запроса.
Ключевое преимущество этого метода заключается в том, что запрос, использующий ключевое слово FINAL, выполняется только один раз во время обновления, устраняя необходимость запускать последующие запросы к целевой таблице с использованием FINAL.
Однако недостаток в том, что данные в целевой таблице актуальны только по состоянию на момент последнего обновления. Тем не менее, для многих сценариев использования интервалы обновления от нескольких минут до нескольких часов могут быть достаточными.
После этого вы можете выполнять запросы к таблице deduplicated_posts в обычном режиме.