Какие ошибки делают команды, когда используют ClickHouse как «просто SQL-базу» без учёта его особенностей?
Команды ошибаются, игнорируя колоночное хранение, MergeTree-особенности и пытаясь делать частые UPDATE/DELETE, маленькие INSERT, JOIN по нескольким большим таблицам как в OLTP.
Типичные ошибки при использовании ClickHouse
ClickHouse — колоночная OLAP СУБД, оптимизированная под аналитические запросы на больших объёмах данных. Попытка работать с ней как с PostgreSQL или MySQL приводит к деградации производительности и ошибкам в логике.
Ошибка 1: частые одиночные INSERT
ClickHouse не предназначен для вставки по одной строке. Каждый INSERT создаёт новую part, которая затем асинхронно мержится. При высокой частоте вставок количество parts превысит лимит parts_to_delay_insert (по умолчанию 150) и сервер начнёт искусственно замедлять вставки, а при достижении parts_to_throw_insert (300) — падать с ошибкой.
-- Плохо: INSERT каждой строки отдельно в цикле
INSERT INTO events VALUES (1, now(), 'click');
INSERT INTO events VALUES (2, now(), 'view');
-- Хорошо: батч минимум от 1000 строк
INSERT INTO events VALUES
(1, now(), 'click'),
(2, now(), 'view'),
-- ... тысячи строк
(1000, now(), 'purchase');
Ошибка 2: UPDATE и DELETE как в реляционных БД
Операции ALTER TABLE ... UPDATE/DELETE в ClickHouse — это мутации. Они не атомарные, выполняются асинхронно, перезаписывают целые части данных и очень дорогие. Мутации нельзя откатить.
-- Это мутация — запустится фоново, может занять минуты/часы
ALTER TABLE events UPDATE status = 'processed' WHERE id = 42;
-- Проверить статус мутации:
SELECT * FROM system.mutations WHERE table = 'events' AND is_done = 0;
Правильный подход — проектировать схему так, чтобы обновления не нужны были вовсе. Используйте движок ReplacingMergeTree для дедупликации или CollapsingMergeTree для отмены строк.
Ошибка 3: игнорирование ORDER BY при запросах
Primary index в ClickHouse — sparse index по колонкам из ORDER BY. Если фильтровать по колонкам, которых нет в начале ORDER BY, ClickHouse вынужден сканировать все гранулы. Команды ставят неподходящий ORDER BY при создании таблицы и удивляются полному скану.
-- Таблица с ORDER BY (date, user_id)
CREATE TABLE events (
date Date,
user_id UInt64,
event_type String
) ENGINE = MergeTree() ORDER BY (date, user_id);
-- Быстро: фильтр по prefix
SELECT count() FROM events WHERE date = '2024-01-01';
-- Медленно: фильтр только по event_type — нет prefix в индексе
SELECT count() FROM events WHERE event_type = 'click';
Ошибка 4: SELECT * вместо нужных колонок
В колоночной БД чтение лишних колонок означает лишние I/O операции. SELECT * убивает главное преимущество ClickHouse.
Ошибка 5: JOIN больших таблиц без учёта алгоритма
По умолчанию ClickHouse использует hash join, загружая правую таблицу в память. JOIN двух многомиллиардных таблиц приведёт к OOM. Нужно использовать словари (dictGet) или правильно настроить join_algorithm.
Ошибка 6: игнорирование TTL и партиционирования
Без TTL данные накапливаются бесконечно. Без партиционирования по дате удаление старых данных потребует дорогих мутаций вместо простого ALTER TABLE DROP PARTITION.
CREATE TABLE events (
date Date,
user_id UInt64,
event_type String
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(date)
ORDER BY (date, user_id)
TTL date + INTERVAL 1 YEAR DELETE;
-- Быстрое удаление старой партиции:
ALTER TABLE events DROP PARTITION '202301';
Подводные камни
- parts_to_throw_insert: при вставке мелкими батчами сервер начинает бросать исключение
Too many parts— никакого graceful degradation нет. - Мутации необратимы: нет ROLLBACK, нет транзакций — ошибочная мутация меняет данные навсегда.
- Eventual consistency при репликации: ReplicatedMergeTree не даёт strong consistency — разные реплики могут возвращать разные результаты в один момент времени.
- NULL в ORDER BY: NULL-значения в ключевых колонках сортируются специфично и могут ломать логику индексирования.
- Тип String вместо LowCardinality: для колонок с малым числом уникальных значений (статусы, типы) надо использовать
LowCardinality(String)— это в 3–5 раз быстрее по памяти и CPU. - Неправильное партиционирование: слишком гранулярное партиционирование (по дню при миллиардах строк в день) создаёт десятки тысяч parts и деградирует производительность.
- Отсутствие материализованных представлений: команды пересчитывают агрегаты на лету вместо хранения преагрегированных данных в Materialized View с AggregatingMergeTree.
- Игнорирование system.query_log: без анализа
system.query_logиsystem.query_thread_logдиагностика проблем идёт вслепую.
What hurts your answer
- Перечислять ошибки без объяснения причин
- Не отличать beginner mistakes от production failure modes
- Не предлагать процесс, который предотвращает повторение ошибок
What they're listening for
- Знает типичные ошибки при работе с ClickHouse
- Понимает причины ошибок
- Предлагает практики prevention и early detection