ClickHouse — прод: вставка, бэкапы, дискClickHouse — production: ingestion, backups, disk

ЭксплуатацияOperations

Правильно спроектированная схема (см. проектирование) может всё равно упасть в проде из-за паттерна вставки. Здесь — как батчить запись, чем бэкапить кластер, и как совмещать TTL с холодным хранилищем, не платя за горячий диск данными, которые никто не читает. Кластеризация — в отдельной главе «Настройка шардов», мониторинг — в «Мониторинге».A well-designed schema (see schema design) can still fall over in production because of the ingestion pattern. Here's how to batch writes, what to back up a cluster with, and how to combine TTL with cold storage without paying hot-disk prices for data nobody reads. Clustering lives in its own "Sharding" chapter, monitoring in "Monitoring."

частый вопросcommon interview question

Тюнинг вставок: батчингInsert tuning: batching

Главное правило продовой эксплуатации ClickHouse: вставлять пачками, а не по одной строке (см. грабли в архитектуре — "Too many parts"). Ориентир — тысячи-десятки тысяч строк за один `INSERT`, не чаще раза в секунду на таблицу. Если источник данных — поток одиночных событий (Kafka, HTTP), между источником и ClickHouse нужен буфер, который накапливает строки и сбрасывает их пачкой: либо движок `Buffer` (промежуточная in-memory таблица перед целевой), либо буферизация на стороне приложения/коллектора перед вставкой.The cardinal rule of running ClickHouse in production: insert in batches, not one row at a time (see the "Too many parts" gotcha in architecture). Aim for thousands to tens of thousands of rows per INSERT, no more than about once a second per table. If the data source is a stream of individual events (Kafka, HTTP), a buffer is needed between the source and ClickHouse that accumulates rows and flushes them as a batch: either the Buffer engine (an intermediate in-memory table in front of the target) or buffering on the application/collector side before inserting.

Асинхронные вставки (`async_insert = 1`) — встроенная альтернатива самостоятельному буферу: сервер сам накапливает мелкие вставки от множества клиентов в один более крупный кусок перед записью на диск, ценой небольшой задержки подтверждения. Полезно, когда источников много и каждый пишет редко и понемногу — типичная ситуация для коллекторов метрик с большим количеством независимых инстансов.Async inserts (async_insert = 1) are a built-in alternative to a hand-rolled buffer: the server itself accumulates small inserts from many clients into one larger part before writing to disk, at the cost of a small acknowledgment delay. Useful when there are many sources each writing rarely and in small amounts — a typical situation for metric collectors with many independent instances.

БэкапыBackups

Штатный `BACKUP`/`RESTORE` (начиная с недавних версий) и сторонний `clickhouse-backup` оба опираются на инкрементальность на уровне кусков — поскольку куски неизменяемы (см. архитектуру), бэкап новой версии копирует только новые/изменённые куски, а не всю таблицу целиком, что делает регулярные бэкапы больших таблиц практически осуществимыми. Хранить бэкапы имеет смысл там же, где и tiered cold-хранилище (S3-совместимое объектное хранилище) — оба сценария используют одну и ту же интеграцию ClickHouse с object storage.Both the built-in BACKUP/RESTORE (in recent versions) and the third-party clickhouse-backup rely on part-level incrementality — since parts are immutable (see architecture), backing up a new version copies only new/changed parts, not the whole table, which makes regular backups of large tables practically feasible. It makes sense to store backups in the same place as tiered cold storage (S3-compatible object storage) — both scenarios use the same ClickHouse integration with object storage.

TTL и холодное хранилищеTTL and cold storage

`TTL` на уровне таблицы или колонки задаёт, что делать со старыми данными автоматически: удалить (`TTL ts + INTERVAL 90 DAY DELETE`), пересжать сильнее (`... RECOMPRESS CODEC(ZSTD(9))`) или перенести на другой storage-policy (`... TO VOLUME 'cold'`) — последнее и есть механизм многоуровневого (tiered) хранения: горячие данные на быстром NVMe, данные старше порога — на дешёвом, но медленном объектном хранилище (S3-совместимом, включая self-hosted MinIO), прозрачно для запросов — движок сам знает, где физически лежит каждый кусок.TTL at the table or column level defines what to do with old data automatically: delete it (TTL ts + INTERVAL 90 DAY DELETE), recompress it harder (... RECOMPRESS CODEC(ZSTD(9))), or move it to a different storage policy (... TO VOLUME 'cold') — the last is the tiered-storage mechanism itself: hot data on fast NVMe, data past a threshold on cheap but slow object storage (S3-compatible, including self-hosted MinIO), transparently to queries — the engine itself knows where each part physically lives.

ALTER TABLE trades
MODIFY TTL
    ts + INTERVAL 30 DAY RECOMPRESS CODEC(ZSTD(9)),
    ts + INTERVAL 180 DAY TO VOLUME 'cold',
    ts + INTERVAL 2 YEAR DELETE;

Разница с полностью внешним подходом (например, старый снапшот-коллектор стакана, который писал прямо в ClickHouse и требовал отдельного внешнего процесса для архивации) в том, что TTL — декларативное правило внутри самой БД: не нужен внешний cron/скрипт, который сам решает, что и когда переносить, — движок делает это как часть штатного фонового цикла слияний.The difference from a fully external approach (say, an old order-book snapshot collector that wrote straight into ClickHouse and needed a separate external process for archiving) is that TTL is a declarative rule inside the DB itself: no external cron/script deciding what to move and when — the engine does it as part of its regular background merge cycle.

ГраблиGotchas

Мелкие вставки от множества независимых источников не лечатся батчингом на стороне одного клиентаSmall inserts from many independent sources aren't fixed by batching on a single client

Если 50 независимых коллекторов пишут по одной-две строки в секунду каждый напрямую в ClickHouse, суммарно это всё равно поток мелких `INSERT`, даже если каждый источник "батчит" свои единичные строки — батчинг должен происходить на общей точке перед ClickHouse (`Buffer`-таблица, Kafka + `async_insert`, промежуточный агрегатор), а не на каждом источнике по отдельности.If 50 independent collectors each write one or two rows per second directly to ClickHouse, the aggregate is still a stream of small inserts even if each source "batches" its own single rows — batching needs to happen at a shared point in front of ClickHouse (a Buffer table, Kafka + async_insert, an intermediate aggregator), not separately at each source.

TTL MOVE не освобождает место немедленноTTL MOVE doesn't free space immediately

Перенос куска на холодный том по TTL происходит в рамках фонового цикла, а не мгновенно по истечении срока — на нагруженной системе с большим количеством кусков возможна задержка, в течение которой "просроченные" данные всё ещё занимают дорогое горячее хранилище. Мониторинг свободного места на горячем томе (через `system.disks`) должен учитывать этот лаг, а не полагаться на TTL как на гарантию мгновенного освобождения места.Moving a part to a cold volume via TTL happens within the background cycle, not instantly on expiry — under load with many parts, there can be a delay during which "expired" data still occupies expensive hot storage. Monitoring free space on the hot volume (via system.disks) should account for this lag rather than treating TTL as a guarantee of immediate space reclamation.

Отсутствие ch_format_schema_path — коллектор стартует, но пишет нулиMissing ch_format_schema_path — the collector starts but writes zeros

Если коллектор использует бинарный формат вставки (например, Protobuf) и путь к файлу схемы для десериализации не настроен, ClickHouse может принять вставку без явной ошибки, но записать пустые/нулевые значения вместо реальных данных — сбой тихий и обнаруживается только на этапе чтения результата, а не на этапе записи. Явная проверка соответствия схемы формата (не полагаться на молчаливый успех вставки) — обязательная часть проверки перед деплоем коллектора.If a collector uses a binary insert format (say, Protobuf) and the schema-file path for deserialization isn't configured, ClickHouse can accept the insert without an explicit error but write empty/zero values instead of real data — a silent failure only caught when reading the result back, not at write time. Explicitly verifying the format schema is wired up (not trusting a silently successful insert) is a mandatory pre-deploy check for a collector.