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/DELETE | is_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-контур.