Обзор
При работе с обновлениями аналитические и транзакционные базы данных значительно различаются из-за различий в их базовых принципах проектирования и целевых сценариях использования. Например, PostgreSQL — это ориентированная на строки реляционная база данных с поддержкой ACID, обеспечивающая надёжные и транзакционные операции обновления и удаления, гарантирующие согласованность и целостность данных с помощью таких механизмов, как Multi-Version Concurrency Control (MVCC). Это позволяет безопасно и предсказуемо вносить изменения даже в средах с высокой конкуррентностью.
Напротив, ClickHouse — это колонко-ориентированная база данных, оптимизированная для аналитических нагрузок с преобладанием чтения и высокопроизводительных операций добавления (append-only). Хотя она изначально поддерживает обновления и удаления «на месте», их необходимо использовать осторожно, чтобы избежать высокой нагрузки на I/O. В качестве альтернативы таблицы можно реорганизовать так, чтобы операции удаления и обновления превращались в операции добавления, обрабатываемые асинхронно и/или во время чтения, что отражает ориентацию на высокопроизводительную ингестию данных и эффективное выполнение запросов, а не на изменение данных в режиме реального времени.
В этом руководстве представлен обзор доступных методов обновления в ClickHouse и даются рекомендации по выбору оптимальной стратегии обновления для вашего сценария использования.
Выбор стратегии обновления
Существует два базовых подхода к обновлению данных в ClickHouse:
- Использование специализированных движков таблиц, которые реализуют обновления через вставки
- Использование декларативных обновлений, таких как команды
UPDATE ... SETилиALTER TABLE ... UPDATE
В каждой из двух категорий выше существует несколько способов обновления данных. У каждого подхода есть свои преимущества и характеристики производительности, и вам следует выбрать соответствующий метод в зависимости от вашей модели данных и объёма данных, который вы планируете обновлять.
Когда использовать специализированные движки таблиц
Специализированные движки таблиц — лучший выбор, когда у вас большие объемы обновлений, частые изменения на уровне строк или необходимо обрабатывать непрерывный поток событий обновления и удаления.
Чаще всего вы будете сталкиваться со следующими движками:
| Движок | Синтаксис | Когда использовать |
|---|---|---|
| ReplacingMergeTree | ENGINE = ReplacingMergeTree | Используйте при обновлении больших объемов данных. Этот движок таблицы оптимизирован для дедупликации данных при слияниях. |
| CoalescingMergeTree | ENGINE = CoalescingMergeTree | Используйте, когда данные приходят фрагментами и вам требуется коалесцирование на уровне столбцов, а не полная замена строк. |
| CollapsingMergeTree | ENGINE = CollapsingMergeTree(Sign) | Используйте при частых обновлениях отдельных строк или в сценариях, когда необходимо поддерживать актуальное состояние объектов, изменяющихся со временем. Например, отслеживание активности пользователей или статистики по статьям. |
Поскольку движки таблиц семейства MergeTree выполняют слияние частей данных в фоновом режиме, они обеспечивают согласованность в конечном счёте, и для обеспечения корректной дедупликации данных в этот промежуточный период при выполнении запросов к таблице необходимо использовать ключевое слово FINAL.
Существуют и другие типы движков, но эти используются чаще всего.
Когда использовать декларативные обновления
Декларативные команды UPDATE могут быть более простыми для несложных операций обновления без необходимости управления логикой дедупликации, но в целом они лучше подходят для обновления меньшего количества строк и при меньшей частоте, чем при использовании специализированных движков.
| Метод | Синтаксис | Когда использовать |
|---|---|---|
| Мутация обновления | ALTER TABLE [table] UPDATE | Используйте, когда данные необходимо немедленно записать на диск (например, для соблюдения требований регуляторов). Негативно влияет на производительность запросов SELECT. |
| Обновления на лету | ALTER TABLE [table] UPDATE | Используйте при обновлении небольших объемов данных. Строки немедленно возвращаются с обновленными данными во всех последующих запросах SELECT, но изначально на диске лишь внутренне помечаются как обновленные. Включите с помощью SET apply_mutations_on_fly = 1;. |
| Легковесные обновления | UPDATE [table] SET ... WHERE | Используйте для обновления небольших объемов данных (до ~10% таблицы). Создает патч-части для немедленной видимости без перезаписи целых столбцов. Добавляет накладные расходы к запросам SELECT, но обеспечивает предсказуемую задержку. |
Обновления данных с использованием специализированных движков таблиц
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.
| Method | Syntax | Best for | Trade-offs |
|---|---|---|---|
| Мутации | ALTER TABLE ... UPDATE | Соответствие нормативным требованиям, требующим физического удаления; редкие массовые обновления | Высокая нагрузка на ввод-вывод; переписываются столбцы |
| Легковесные обновления | UPDATE ... SET ... WHERE | Небольшие обновления (~0,1–10% строк); частые обновления, требующие высокой производительности | Увеличивает накладные расходы на SELECT; patch-части учитываются в лимитах |
| Мутации налету | ALTER TABLE ... UPDATE с apply_mutations_on_fly=1 | Немедленная видимость; ограниченное число операций | Зависимость от Keeper; масштабирование ограничено десятками операций |
Мутации
Мутации (ALTER TABLE ... UPDATE) перезаписывают все части, содержащие строки, удовлетворяющие условию WHERE. Это гарантирует физическое изменение данных на диске.
Используйте Мутации, когда нормативные требования требуют гарантированного физического удаления данных или для редких операций массового обновления, где вы можете допустить накладные расходы на I/O.
Мутации создают значительную нагрузку на I/O, так как переписывают все части, которые соответствуют выражению WHERE. В этом процессе нет атомарности — части заменяются на Мутации, как только они готовы, и запрос SELECT, который начинает выполняться во время Мутации, будет видеть данные как из уже изменённых частей, так и из тех, которые ещё не были изменены. Вы можете отслеживать ход выполнения через таблицу system.mutations.
Мутации создают значительную нагрузку на I/O и должны применяться ограниченно, так как они могут влиять на производительность запросов SELECT в кластере. Если Мутации ставятся в очередь быстрее, чем успевают обрабатываться, производительность запросов будет ухудшаться. Отслеживайте очередь через system.mutations.
Подробнее: ALTER TABLE UPDATE
Легковесные обновления
Легковесные обновления используют "patch parts" — специальные части данных, которые содержат только обновлённые столбцы и строки, — вместо перезаписи целых столбцов, как при традиционных Мутациях.
Этот подход использует стандартный синтаксис UPDATE и создает части‑патчи немедленно, не дожидаясь слияний. Обновленные значения сразу видны в запросах SELECT за счет применения патчей, но физически материализуются только во время последующих слияний. Это делает легковесное обновление оптимальным для изменения небольшой доли строк (до ~10% таблицы) с предсказуемой задержкой. Бенчмарки показывают, что такие операции могут быть до 23 раз быстрее, чем Мутации.
Компромисс состоит в том, что запросы SELECT создают дополнительные накладные расходы при применении патчей, а части‑патчи учитываются в лимитах частей. При превышении порога ~10% накладные расходы механизма patch‑on‑read растут пропорционально, что делает синхронные Мутации более эффективными для крупных обновлений.
Подробнее: Lightweight UPDATE
Мутации на лету
Мутации на лету предоставляют механизм обновления строк таким образом, что последующие запросы SELECT автоматически возвращают изменённые значения без ожидания завершения фоновой обработки. Это эффективно устраняет ограничение обычных мутаций, связанное с атомарностью.
И для мутации, и для последующих запросов SELECT необходимо включить настройку apply_mutations_on_fly = 1. Условия мутаций хранятся в ClickHouse Keeper, который хранит всё в памяти, и применяются на лету во время выполнения запросов.
Обратите внимание, что для обновления данных по‑прежнему используется мутация — она просто не материализуется сразу. Мутация всё равно будет применена в фоновом режиме как асинхронный процесс и несёт такую же серьёзную нагрузку, как и обычная мутация. Выражения, которые можно использовать с этой операцией, также ограничены (см. подробности).
Мутации на лету следует использовать только для небольшого числа операций — максимум нескольких десятков. Keeper хранит условия в памяти, поэтому чрезмерное использование влияет на стабильность кластера. Сильная нагрузка на Keeper может вызывать тайм-ауты сессий, затрагивающие другие таблицы.
Подробнее: Мутации на лету
Итоги сравнения
В следующей таблице обобщены накладные расходы на выполнение запросов на основе результатов тестирования. Мутации приняты за базовый уровень, поскольку запросы выполняются на полной скорости после завершения мутации и физической перезаписи данных.
| Method | Query slowdown | Memory overhead | Notes |
|---|---|---|---|
| Mutations | Базовый уровень | Базовый уровень | Полная скорость после завершения; данные физически перезаписываются |
| On-the-fly mutations | Переменное | Переменное | Мгновенная видимость; производительность ухудшается, если накапливается много обновлений |
| Lightweight updates | 7–18% (в среднем ~12%) | +20–210% | Наиболее эффективен для запросов; оптимален для обновления ≤10% таблицы |
ReplacingMergeTree + FINAL | 21–550% (в среднем ~280%) | В 20–200 раз больше базового уровня | Необходимо читать все версии строк; самые высокие накладные расходы на запросы |
CoalescingMergeTree + FINAL | Аналогично ReplacingMergeTree | Аналогично ReplacingMergeTree | Объединение на уровне столбцов добавляет сопоставимые накладные расходы |
| CollapsingMergeTree | Зависит от агрегации | Зависит от агрегации | Накладные расходы зависят от сложности запроса |
Дополнительные ресурсы
Если вам интересен подробный разбор того, как со временем эволюционировали обновления в ClickHouse, а также анализ бенчмарков, см. следующие материалы: