КИБЕРБЕЗОПАСНОСТЬ

Эксперт объяснил, почему FINAL в ClickHouse — признак неправильной архитектуры

Разработчик объяснил, почему частое использование FINAL в ClickHouse убивает производительность и как правильно проектировать схемы без него.

✍️ Редакция iTech News | 10.03.2026 | ⏱ 2 мин
🕵

Если вы регулярно используете FINAL в ClickHouse, скорее всего, ваша схема базы этих спроектирована неправильно. Эта операция превращает быструю аналитическую систему в медленную транзакционную — и убивает производительность на больших объёмах данных.

Проблема в том, что FINAL кажется волшебным решением. Видите дубликаты — добавляете FINAL, и всё выглядит правильно. Но за этим «исправлением» скрывается серьёзная архитектурная проблема.

Почему в ClickHouse вообще появляются дубликаты

ClickHouse работает не как обычная OLTP-база. В движках семейства MergeTree каждая вставка создаёт новую неизменяемую часть данных. Фоновые процессы асинхронно объединяют эти части, и только тогда происходит дедупликация.

В ReplacingMergeTree строки с одинаковыми первичными ключами могут временно сосуществовать в разных частях. Они исчезают только после фонового слияния — до этого момента дубликаты видны в запросах.

Это не баг, а особенность архитектуры. ClickHouse жертвует мгновенной согласованностью ради скорости записи и асинхронного сжатия.

Как FINAL убивает производительность

Когда вы пишете SELECT * FROM table FINAL, вы заставляете ClickHouse выполнить слияние этих прямо во время запроса. Система должна:

  • Прочитать все релевантные части данных
  • Применить логику дедупликации в памяти
  • Выполнить процесс, похожий на слияние
  • Отключить определённые оптимизации чтения

На небольших датасетах вы этого не заметите. Но на крупных аналитических таблицах FINAL превращает OLAP-систему в нечто, подозрительно похожее на транзакционную базу с построчной сверкой данных.

А для этого ClickHouse не оптимизирована.

Правильные альтернативы FINAL

Вместо регулярного использования FINAL лучше пересмотреть архитектуру:

Используйте колонку версии правильно. В ReplacingMergeTree определите версию и стройте запросы через агрегацию: SELECT id, argMax(value, version) FROM table GROUP BY id.

Моделируйте неизменяемые события. Вместо перезаписи строк сохраняйте события в append-only формате. Вычисляйте актуальное состояние через представления или агрегации.

Используйте предварительную агрегацию. Материализованные представления, AggregatingMergeTree и SummingMergeTree переносят вычисления на момент вставки данных.

Пересмотрите стратегию партицирования. Иногда дубликаты накапливаются из-за слишком больших партиций или неоптимальных настроек слияния.

Когда FINAL всё-таки оправдан

FINAL имеет смысл использовать для отладочных запросов, работы с небольшими таблицами или валидации данных. Но если это становится частью регулярных запросов к продакшн-системе — стоит пересмотреть дизайн схемы.

Помните: лучше потратить время на правильную архитектуру этих сейчас, чем бороться с проблемами производительности потом.

Поделиться: Telegram X LinkedIn