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

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

Репликация: ReplicatedMergeTree и Keeper

Репликация в 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, ZooKeeper остался в исторических). Keeper хранит метаданные: какие parts существуют, очередь репликации, кто лидер по партиции. Сами данные ходят между репликами напрямую.

Свойства, которые надо знать сервису:

  • Репликация асинхронная и мультимастерная: писать можно в любую реплику, остальные подтянут. Гарантию «вставка видна на всех» даёт insert_quorum, но обычный пайплайн аналитики живёт без него.
  • Чтение с другой реплики сразу после вставки может не увидеть данные — для read-after-write есть select_sequential_consistency, но лучше просто не строить на ClickHouse сценарии, где это важно.
  • Block-дедупликация вставок (см. интеграцию) работает через Keeper — у Replicated-таблиц она включена по умолчанию.

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

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

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

Механика: данные режутся по шардам, поверх — таблица-маршрутизатор ENGINE = Distributed(cluster, db, local_table, sharding_key). Запрос в Distributed разлетается по шардам и агрегируется на инициаторе; вставка — либо через Distributed (он раскидает по ключу), либо напрямую в локальные таблицы шардов (надёжнее под нагрузкой — меньше промежуточной буферизации).

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

TTL: жизненный цикл данных

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

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

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

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

Связка «сырые события с TTL 12 месяцев + materialized view с агрегатами навсегда» — типовая: дашборды живут на агрегатах, сырьё для ad-hoc анализа есть за последний год, диск под контролем. Удаление целыми партициями (ALTER TABLE ... DROP PARTITION) остаётся самым дешёвым способом чистки — ещё один аргумент за месячные партиции.

Бэкапы

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

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

Бэкапы инкрементальные (по parts — неизменяемость окупается и здесь), целиться стоит в S3. Для кластеров постарше распространён clickhouse-backup (сторонняя утилита с тем же принципом). Отдельная страховка аналитического контура — возможность перезалить данные из источника: пока Kafka хранит события (retention!) или PG хранит историю, ClickHouse восстановим пересборкой; это дешёвая «вторая линия», но она исчезает, как только retention в Kafka короче глубины данных в ClickHouse.

Мониторинг: system-таблицы

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

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

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

Со стороны Java-сервиса мониторится пайплайн: lag консьюмера Kafka (события копятся — ClickHouse отстаёт от прода) и доля неудачных вставок. Это те же правила, что в observability-гайде, применённые ко второму хранилищу.

Типичные инциденты

  • TOO_MANY_PARTS / вставка отвергнута. Причина почти всегда в писателе: мелкие частые INSERT-ы, слишком дробное партиционирование или каскад MV, размножающий вставки. Лечится батчингом и схемой, не подкруткой лимита.
  • OOM на тяжёлом GROUP BY. Агрегация по высококардинальной колонке не влезла в max_memory_usage. Быстрое средство — max_bytes_before_external_group_by (spill на диск: медленнее, но доживёт); системное — предагрегация через MV.
  • Реплики разъехались. Смотреть в system.replication_queue и логи Keeper: чаще всего — сеть до Keeper или диск. Реплика чинится SYSTEM RESTART REPLICA / SYSTEM RESTORE REPLICA; данные дольёт репликация.
  • Мутации стоят. Очередь ALTER ... DELETE (например, GDPR-чистки) блокируется нехваткой диска или ошибкой в одной мутации — KILL MUTATION для зависшей, дальше разбор причины.
  • Запрос-убийца от аналитика. SELECT * за два года без фильтра. Защита — профили пользователей: max_execution_time, max_memory_usage, max_rows_to_read на read-only пользователях; quota на частоту. Ставится до инцидента, не после.

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

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

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

  • Fundamentals — parts и merges, из которых растёт половина этого раздела.
  • Интеграция из Java/Spring — пайплайн, который эти алерты охраняют.
  • Elasticsearch Operations — сравните: те же мотивы (tiering, snapshots, sizing) в соседнем хранилище.
  • Observability Style Guide — метрики и алерты сервиса, к которым добавляется ClickHouse-контур.