Обновления в ClickHouse
Когда речь идёт об обновлениях, аналитические и транзакционные базы данных используют разные подходы, обусловленные базовыми принципами их архитектуры и целевыми сценариями использования. ClickHouse — это столбцовая база данных, оптимизированная для аналитических нагрузок с преобладанием чтения и операций только добавления с высокой пропускной способностью. На практике таблицы часто перестраивают так, чтобы преобразовать удаления и обновления в операции добавления, которые обрабатываются асинхронно и/или во время чтения, используя сильные стороны ClickHouse для высокопроизводительной ингестии данных. ClickHouse также поддерживает полноценные операции обновления и удаления.
Это руководство даёт обзор доступных в ClickHouse методов обновления и помогает выбрать подходящую стратегию обновления для вашей нагрузки.
Выбор стратегии обновления
Существует два базовых подхода к обновлению данных в ClickHouse:
- Использование специализированных движков таблиц, которые реализуют обновления через вставки
- Использование декларативных обновлений, таких как команды
UPDATE ... SETилиALTER TABLE ... UPDATE
В каждой из двух категорий выше существует несколько способов обновления данных. У каждого подхода есть свои преимущества и характеристики производительности, и вам следует выбрать соответствующий метод в зависимости от вашей модели данных и объёма данных, который вы планируете обновлять.
Когда использовать специализированные движки таблиц
Специализированные движки таблиц — лучший выбор, когда у вас большие объемы обновлений, частые изменения на уровне строк или необходимо обрабатывать непрерывный поток событий обновления и удаления.
Чаще всего вы будете сталкиваться со следующими движками:
| Движок | Синтаксис | Когда использовать |
|---|---|---|
| ReplacingMergeTree | ENGINE = ReplacingMergeTree | Используйте при обновлении больших объемов данных. Этот движок таблицы оптимизирован для дедупликации данных при слияниях. |
| CoalescingMergeTree | ENGINE = CoalescingMergeTree | Используйте, когда данные приходят фрагментами и вам требуется коалесцирование на уровне столбцов, а не полная замена строк. |
| CollapsingMergeTree | ENGINE = CollapsingMergeTree(Sign) | Используйте при частых обновлениях отдельных строк или в сценариях, когда необходимо поддерживать актуальное состояние объектов, изменяющихся со временем. Например, отслеживание активности пользователей или статистики по статьям. |
Поскольку движки таблиц семейства MergeTree выполняют слияние частей данных в фоновом режиме, они обеспечивают согласованность в конечном счёте, и для обеспечения корректной дедупликации данных в этот промежуточный период при выполнении запросов к таблице необходимо использовать ключевое слово FINAL.
Существуют и другие типы движков, но эти используются чаще всего.
Когда использовать декларативные обновления
Декларативные операторы UPDATE могут быть более простыми для несложных операций обновления без необходимости управления логикой дедупликации, но в целом они лучше подходят для обновления меньшего количества строк и при меньшей частоте, чем при использовании специализированных движков.
| Метод | Синтаксис | Когда использовать |
|---|---|---|
| Легковесные обновления | UPDATE [table] SET ... WHERE | Используйте это в большинстве сценариев, особенно при выполнении частых небольших UPDATE (до ~10% таблицы) в рамках вашего приложения или рабочих процессов. Например, пользователь хочет удалить свою историю событий, а сами события распределены по мультитенантной таблице со множеством пользователей. Создаёт патч-части для немедленной видимости без перезаписи целых столбцов. Добавляет накладные расходы к запросам SELECT, но обеспечивает предсказуемую задержку. |
| Мутация обновления | ALTER TABLE [table] UPDATE | Используйте это при выполнении более масштабных операций управления данными, особенно когда обновление соответствует схеме партиционирования таблицы. Например, если нужно обновить столбец во всех строках за месяц в таблице, разбитой на партиции по месяцам. |
Обновления данных с использованием специализированных движков таблиц
ReplacingMergeTree
ReplacingMergeTree удаляет дубликаты строк с одинаковым ключом сортировки во время фоновых слияний, сохраняя только самую новую версию.
Этот движок идеально подходит для высокочастотных обновлений отдельных строк, где обновления идентифицируются по стабильному ключу. Бенчмарки показывают, что он может быть до 4 700 раз быстрее, чем мутации, при обновлениях одной строки.
Чтобы обновить строку, просто вставьте новую версию с теми же значениями ключа сортировки и более высоким номером версии. Старые версии удаляются во время фоновых слияний. Поскольку дедупликация выполняется не сразу (она происходит только во время слияний), следует использовать модификатор FINAL или эквивалентную логику запроса, чтобы получить корректные, дедуплицированные результаты. Модификатор FINAL добавляет накладные расходы на выполнение запроса в диапазоне от 21 до 550% в зависимости от данных.
ReplacingMergeTree не может изменять значения ключа сортировки. Он также поддерживает столбец Deleted для логического удаления.
Подробнее: Руководство по ReplacingMergeTree | Справочник по ReplacingMergeTree
CoalescingMergeTree
CoalescingMergeTree консолидирует разреженные записи, сохраняя последнее ненулевое значение для каждого столбца при слияниях. Это позволяет выполнять операции upsert на уровне столбцов, а не полную замену всей строки.
Этот движок предназначен для сценариев, когда данные поступают фрагментами из нескольких источников или когда разные столбцы заполняются в разное время. Типичные варианты использования включают IoT‑телеметрию от фрагментированных подсистем, обогащение пользовательских профилей и ETL‑конвейеры с отложенными измерениями.
При слиянии строк с одинаковым сортировочным ключом CoalescingMergeTree сохраняет последнее ненулевое (не NULL) значение для каждого столбца вместо замены всей строки. Неключевые столбцы должны быть типа Nullable, чтобы это работало как задумано. Как и в ReplacingMergeTree, используйте FINAL для корректных, коалесцированных результатов.
Этот движок доступен начиная с ClickHouse 25.6.
Подробнее: CoalescingMergeTree
CollapsingMergeTree
Исходя из идеи, что операции обновления дорогостоящи, тогда как вставки можно использовать для реализации обновлений, CollapsingMergeTree использует столбец Sign, чтобы указать ClickHouse, как обрабатывать строки во время слияний. Если для столбца Sign вставлено значение -1, строка будет свернута (удалена) при объединении с соответствующей строкой с +1. Строки для обновления определяются на основе ключа сортировки, указанного в предложении ORDER BY при создании таблицы.
В отличие от ReplacingMergeTree, CollapsingMergeTree позволяет изменять значения ключа сортировки. Он хорошо подходит для обратимых операций с семантикой отмены, таких как финансовые транзакции или отслеживание состояния игры.
Подход к обновлению, описанный выше, требует, чтобы ваше приложение поддерживало состояние на стороне клиента, чтобы вставлять строку отмены. Хотя это наиболее эффективно с точки зрения ClickHouse, с этим может быть сложно работать в крупном масштабе. Запросы также должны выполнять агрегацию с умножением на знак, чтобы получать корректные результаты.
Подробнее: CollapsingMergeTree
Декларативные обновления
Эти методы работают с таблицами на движках семейства MergeTree.
| метод | синтаксис | Best for | Trade-offs |
|---|---|---|---|
| Мутации | ALTER TABLE ... UPDATE | Редкие массовые обновления; особенно хорошо подходят, когда обновление согласуется с партиционированием таблицы. | Высокая нагрузка на ввод-вывод; переписываются столбцы |
| Легковесные обновления | UPDATE ... SET ... WHERE | Небольшие обновления (~0,1–10% строк); частые обновления, требующие высокой производительности | Увеличивает накладные расходы на SELECT; патч-части учитываются в лимитах |
Мутации
Мутации (ALTER TABLE ... UPDATE) перезаписывают все части, содержащие строки, удовлетворяющие условию WHERE.
Мутации создают значительную нагрузку на I/O, так как переписывают все части, которые соответствуют выражению WHERE.
В этом процессе нет атомарности.
Части заменяются на Мутации, как только они готовы, и запрос SELECT, который начинает выполняться во время Мутации, будет видеть данные как из уже изменённых частей, так и из тех, которые ещё не были изменены.
Вы можете отслеживать ход выполнения через таблицу system.mutations.
Мутации создают значительную нагрузку на I/O и должны применяться ограниченно, так как они могут влиять на производительность запросов SELECT в кластере. Если Мутации ставятся в очередь быстрее, чем успевают обрабатываться, производительность запросов будет ухудшаться. Отслеживайте очередь через system.mutations.
Подробнее: ALTER TABLE UPDATE
Мутации на лету
При использовании мутаций через ALTER TABLE ... UPDATE может потребоваться подождать, пока они будут применены фоновым процессом, прежде чем измененные значения начнут отражаться в результатах запросов.
ClickHouse позволяет изменить это поведение с помощью "мутаций на лету".
Когда мутации на лету включены, обновленные строки сразу помечаются как обновленные, и последующие запросы SELECT автоматически возвращают измененные значения.
Мутации на лету можно включить для таблиц семейства MergeTree, включив настройку уровня запроса apply_mutations_on_fly.
Пример
Создадим таблицу и выполним несколько мутаций:
Проверим результат обновлений с помощью запроса SELECT:
Обратите внимание: на момент выполнения запроса значения строк в таблице еще не обновлены:
Теперь посмотрим, что произойдет, если включить мутации на лету:
Теперь запрос SELECT сразу возвращает правильный результат, без ожидания применения мутаций:
Влияние на производительность
Когда включены мутации на лету, мутации материализуются не сразу, а применяются только при выполнении запросов SELECT. Однако обратите внимание, что мутации по-прежнему асинхронно материализуются в фоновом режиме, а это ресурсоемкий процесс.
Если количество отправленных мутаций в течение некоторого интервала времени постоянно превышает количество мутаций, обрабатываемых в фоновом режиме, очередь нематериализованных мутаций, которые необходимо применить, будет продолжать расти. В конечном итоге это приведет к ухудшению производительности запросов SELECT.
Мы рекомендуем включать настройку apply_mutations_on_fly вместе с другими настройками уровня MergeTree, такими как number_of_mutations_to_throw и number_of_mutations_to_delay, чтобы ограничить неограниченный рост числа нематериализованных мутаций.
Поддержка подзапросов и недетерминированных функций
Мутации на лету ограниченно поддерживают подзапросы и недетерминированные функции. Поддерживаются только скалярные подзапросы с результатом разумного размера (это определяется настройкой mutations_max_literal_size_to_replace). Также поддерживаются только константные недетерминированные функции (например, функция now()).
Эти особенности управляются следующими настройками:
| Настройка | Описание | По умолчанию |
|---|---|---|
mutations_execute_nondeterministic_on_initiator | Если установлено значение true, недетерминированные функции выполняются на реплике-инициаторе и заменяются литералами в запросах UPDATE и DELETE. | false |
mutations_execute_subqueries_on_initiator | Если установлено значение true, скалярные подзапросы выполняются на реплике-инициаторе и заменяются литералами в запросах UPDATE и DELETE. | false |
mutations_max_literal_size_to_replace | Максимальный размер сериализованных литералов в байтах, которыми заменяются значения в запросах UPDATE и DELETE. | 16384 (16 KiB) |
Легковесные обновления
Легковесные обновления используют "patch parts" — специальные части данных, которые содержат только обновлённые столбцы и строки, — вместо перезаписи целых столбцов, как при традиционных Мутациях.
Этот подход использует стандартный синтаксис UPDATE и создает части‑патчи немедленно, не дожидаясь слияний. Обновленные значения сразу видны в запросах SELECT за счет применения патчей, но физически материализуются только во время последующих слияний. Это делает легковесное обновление оптимальным для изменения небольшой доли строк (до ~10% таблицы) с предсказуемой задержкой. Бенчмарки показывают, что такие операции могут быть до 23 раз быстрее, чем Мутации.
Компромисс состоит в том, что запросы SELECT создают дополнительные накладные расходы при применении патчей, а части‑патчи учитываются в лимитах частей. При превышении порога ~10% накладные расходы механизма patch‑on‑read растут пропорционально, что делает синхронные Мутации более эффективными для крупных обновлений.
Подробнее: Lightweight UPDATE
Мутации на лету
Мутации на лету предоставляют механизм обновления строк таким образом, что последующие запросы SELECT автоматически возвращают изменённые значения без ожидания завершения фоновой обработки. Это эффективно устраняет ограничение обычных мутаций, связанное с атомарностью.
И для мутации, и для последующих запросов SELECT необходимо включить настройку apply_mutations_on_fly = 1. Условия мутаций хранятся в ClickHouse Keeper, который хранит всё в памяти, и применяются на лету во время выполнения запросов.
Обратите внимание, что для обновления данных по‑прежнему используется мутация — она просто не материализуется сразу. Мутация всё равно будет применена в фоновом режиме как асинхронный процесс и несёт такую же серьёзную нагрузку, как и обычная мутация. Выражения, которые можно использовать с этой операцией, также ограничены (см. подробности).
Мутации на лету следует использовать только для небольшого числа операций — максимум нескольких десятков. Keeper хранит условия в памяти, поэтому чрезмерное использование влияет на стабильность кластера. Сильная нагрузка на Keeper может вызывать тайм-ауты сессий, затрагивающие другие таблицы.
Подробнее: Мутации на лету
Итоги сравнения
В следующей таблице обобщены накладные расходы на выполнение запросов на основе бенчмарков. Мутации приняты за базовый уровень, поскольку запросы выполняются на полной скорости после завершения мутации и физической перезаписи данных.
| Метод | Замедление запросов | Накладные расходы по памяти | Примечания |
|---|---|---|---|
| Мутации | Базовый уровень | Базовый уровень | Полная скорость после завершения; данные физически перезаписываются |
| Мутации на лету | Переменное | Переменное | Мгновенная видимость; производительность ухудшается, если накапливается много обновлений |
| Легковесные обновления | 7–18% (в среднем ~12%) | +20–210% | Наиболее эффективны для запросов; лучше всего подходят для обновления ≤10% таблицы |
ReplacingMergeTree + FINAL | 21–550% (в среднем ~280%) | В 20–200 раз больше базового уровня | Необходимо читать все версии строк; самые высокие накладные расходы на запросы |
CoalescingMergeTree + FINAL | Аналогично ReplacingMergeTree | Аналогично ReplacingMergeTree | Объединение на уровне столбцов добавляет сопоставимые накладные расходы |
| CollapsingMergeTree | Зависит от агрегации | Зависит от агрегации | Накладные расходы зависят от сложности запроса |
Дополнительные ресурсы
Если вам интересен подробный разбор того, как со временем эволюционировали обновления в ClickHouse, а также анализ бенчмарков, см. следующие материалы: