ClickHouseMiddleExperience

Какие ошибки делают команды, когда используют 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

Related topics