← назад к разделу

ClickHouse прощает многое — один сервер тянет миллиарды строк, — но у него свой набор эксплуатационных сложностей, не похожих ни на PostgreSQL, ни на Kafka. Разберём, что настраивают до прода и на что смотрят после.

Репликация: зачем и как устроена

Если ClickHouse стоит в одном экземпляре и сервер упадёт — данные недоступны. Дублировать их на несколько машин помогает репликация.

В ClickHouse репликация — на уровне таблицы, не сервера. Реплицируются только те таблицы, чей движок принадлежит семейству Replicated*MergeTree:

CREATE TABLE order_events ON CLUSTER main
(
    event_time   DateTime,
    event_type   LowCardinality(String),
    order_id     UUID,
    amount       Decimal(18, 2)
)
ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/order_events', '{replica}')
PARTITION BY toYYYYMM(event_time)
ORDER BY (event_type, event_time);

Координатор репликации — ClickHouse Keeper (встроенная замена ZooKeeper; для новых установок — только Keeper). Keeper хранит метаданные: какие куски данных существуют, очередь репликации, кто лидер по партиции. Сами данные ходят между репликами напрямую.

Важные свойства:

  • Репликация асинхронная и мультимастерная — писать можно в любую реплику, остальные подтянут. Чтение с другой реплики сразу после записи может не увидеть данные — это нормально для аналитических сценариев, но не строить на этом строгую согласованность.
  • Вставка одних и тех же данных дважды не задвоит строки: дедупликация вставок через Keeper включена для Replicated-таблиц по умолчанию.

Минимальная продуктивная топология: 2 реплики + 3 ноды Keeper (Keeper можно совмещать с ClickHouse-нодами на небольших кластерах). Одна нода без репликации допустима, если данные можно перезалить из Kafka или PostgreSQL; если ClickHouse — единственное место хранения событий, одна нода не подходит.

Шардинг: позже, чем кажется

Типичная ошибка — шардировать сразу. Один сервер ClickHouse обрабатывает терабайты; пока вертикальный рост (диск, память) и правильная схема справляются — шардинг не нужен. Он нужен, когда данные физически не помещаются на одной машине или когда один запрос упирается в CPU одного сервера.

Механика: данные разрезаются по шардам, поверх — таблица-маршрутизатор:

ENGINE = Distributed(cluster, db, local_table, sharding_key)

Запрос в Distributed разлетается по шардам и собирается на инициаторе. Вставка — либо через Distributed (он раскидает по ключу), либо напрямую в локальные таблицы шардов (надёжнее под нагрузкой, меньше промежуточной буферизации).

Выбор sharding_key: равномерность важнее смысла. cityHash64(order_id) распределит ровно; шардинг «по региону» даст горячий шард на крупном регионе.

TTL: когда старые данные дорого хранить

Аналитические данные стареют, и хранить сырые события вечно накладно. TTL решает это декларативно:

ALTER TABLE order_events
    MODIFY TTL event_time + INTERVAL 12 MONTH DELETE;

Варианты политики:

  • DELETE — удалить старые строки (фоном, при слиянии кусков).
  • TO DISK 'cold' / TO VOLUME 'cold' — многоуровневое хранение: свежие месяцы на быстрых дисках, старые — на дешёвых или S3. Это то, что в других системах называют горячий/тёплый/холодный уровень.
  • GROUP BY ... SET — понижение точности: старше года хранить не события, а суточные агрегаты.

Типовая связка: сырые события с TTL 12 месяцев + материализованное представление с агрегатами навсегда. Дашборды живут на агрегатах, сырьё для точечного анализа есть за последний год, диск под контролем.

Удаление целыми партициями — самый дешёвый способ чистки:

ALTER TABLE order_events DROP PARTITION '202401';

Это ещё один аргумент за месячные партиции.

Бэкапы

Реплика — не бэкап: команда DROP TABLE реплицируется так же исправно, как INSERT. Для настоящего резервного копирования — встроенный механизм:

BACKUP TABLE analytics.order_events
    TO S3('https://s3.../backups/order_events/2026-06', '...', '...');

RESTORE TABLE analytics.order_events
    FROM S3('https://s3.../backups/order_events/2026-06', '...', '...');

Бэкапы инкрементальные: неизменяемость кусков (parts) позволяет копировать только новые. Целиться стоит в S3.

Дополнительная страховка аналитического контура — возможность перезалить данные из источника. Пока Kafka хранит события или PostgreSQL хранит историю, ClickHouse восстановим пересборкой. Эта «вторая линия» исчезает, как только срок хранения в Kafka короче глубины данных в ClickHouse.

Мониторинг через system-таблицы

ClickHouse рассказывает о себе SQL-запросами — вся диагностика в схеме system:

ТаблицаЧто смотретьПорог для алерта
system.partsЧисло активных кусков на таблицу-партицию> 300 на партицию — вставка скоро встанет
system.mergesТекущие слияния, прогрессСлияния не успевают за вставкой
system.mutationsОчередь ALTER UPDATE/DELETEis_done = 0 несколько часов
system.replication_queueОтставание репликОчередь растёт и не разгребается
system.query_logКто, что и сколько читалЗапросы с чтением > N ГБ или > N секунд
system.disksСвободное место< 20% — слияниям нужен запас под копию кусков

Экспорт метрик в Prometheus встроен — секция <prometheus> в конфиге сервера, дальше стандартные дашборды и алерты. Стартовый набор алертов: слишком много кусков (TOO_MANY_PARTS), отставание репликации, диск, доля упавших запросов, длительность фоновых изменений.

Типичные проблемы и их причины

TOO_MANY_PARTS — вставка отвергнута. Причина почти всегда в пишущем сервисе: мелкие частые INSERT-ы, слишком дробное партиционирование или каскад материализованных представлений, размножающий вставки. Решается группировкой вставок и пересмотром схемы, а не подкруткой лимита.

OOM на тяжёлом GROUP BY. Агрегация по высококардинальной колонке не влезла в max_memory_usage. Быстрое средство — max_bytes_before_external_group_by (выгрузка на диск: медленнее, но запрос доживёт); системное решение — предагрегация через материализованные представления.

Реплики разошлись. Смотреть в system.replication_queue и логи Keeper: чаще всего причина — сеть до Keeper или заполненный диск. Реплика восстанавливается командами:

SYSTEM RESTART REPLICA table_name;
SYSTEM RESTORE REPLICA table_name;

Данные дольёт репликация самостоятельно.

Изменения (мутации) стоят. Очередь ALTER ... DELETE (например, удаление данных по требованию регуляторов) блокируется нехваткой диска или ошибкой в одной мутации. Зависшую мутацию можно отменить через KILL MUTATION, после чего разбирать причину.

Запрос-убийца. SELECT * за два года без фильтра. Защита — профили пользователей: max_execution_time, max_memory_usage, max_rows_to_read для read-only пользователей, квоты на частоту. Эти ограничения ставят до инцидента.

Версии и обновление

ClickHouse выпускается ежемесячно; для продуктивных систем — LTS-релизы (два в год, год поддержки). Обновление кластера — пореплично, без остановки: реплика выводится, обновляется, догоняет очередь репликации, затем следующая. Перед крупным обновлением — прогон рабочих запросов на тестовом окружении: оптимизатор и поведение настроек между LTS-версиями меняются заметнее, чем в PostgreSQL.

Коротко

  • Репликация — на уровне таблицы, движок ReplicatedMergeTree; координирует Keeper.
  • Минимальная продуктивная топология: 2 реплики + 3 ноды Keeper.
  • Шардинг нужен позже, чем кажется — один сервер тянет терабайты; добавляйте, когда вертикальный рост исчерпан.
  • TTL управляет жизнью данных: удаление, перемещение на холодный диск или понижение точности.
  • Реплика — не бэкап; DROP TABLE реплицируется. Бэкап — BACKUP TO S3.
  • Вся диагностика — SQL через system.* таблицы; экспорт метрик в Prometheus встроен.
  • Самая частая авария: TOO_MANY_PARTS — решается группировкой вставок, не подкруткой лимита.

Что почитать дальше

  • Fundamentals — куски и слияния, из которых растёт половина этого раздела.
  • Интеграция с ClickHouse — пайплайн, который эти алерты охраняют.
  • Elasticsearch: эксплуатация — схожие темы (тиеринг, снэпшоты, размер кластера) в соседнем хранилище.