Прежде чем писать манифесты, нужно понимать, что именно вы разворачиваете. ClickHouse устроен иначе, чем привычные OLTP-базы, и половина проблем в Kubernetes растёт из попытки эксплуатировать его «как обычный stateless-деплоймент».
MergeTree: куски, гранулы, слияния
Основное семейство движков — MergeTree. Модель простая и жёсткая:
- Каждая вставка создаёт новый иммутабельный кусок данных (part) на диске. Куски не изменяются.
- Внутри куска данные отсортированы по
ORDER BYи разбиты на гранулы — минимальные блоки чтения (по умолчаниюindex_granularity = 8192строк). - Первичный индекс — разрежённый: одна запись на гранулу, а не на строку. Поэтому он компактный и целиком лежит в памяти.
- Фоновые потоки сливают мелкие куски в крупные (merge). Это постоянная фоновая работа, потребляющая CPU и диск.
Отсюда сразу следуют два практических вывода, к которым мы вернёмся в частях про схему и вставку:
- Много мелких вставок — это яд. Каждая создаёт кусок, слияния не успевают, вы получаете ошибку
too many partsи деградацию. ORDER BYопределяет производительность сильнее, чем что-либо ещё: он же порядок хранения, он же индекс, он же фактор сжатия.
Шард или реплика
Два принципиально разных способа «добавить узлов»:
- Реплика — полная копия данных шарда. Даёт отказоустойчивость и масштабирование чтения. Реализуется движком
ReplicatedMergeTree. - Шард — часть данных. Даёт масштабирование объёма и записи. Запросы поверх шардов собирает движок
Distributed.
Их комбинируют: например, 3 шарда × 2 реплики = 6 узлов.
flowchart TD
C["Клиент"] --> D["Distributed-таблица<br/>(объединяет шарды)"]
subgraph S1["Шард 1"]
R11[("Реплика A")] <--> R12[("Реплика B")]
end
subgraph S2["Шард 2"]
R21[("Реплика A")] <--> R22[("Реплика B")]
end
D --> S1
D --> S2
S1 -.координация.-> K["ClickHouse Keeper<br/>(кворум из 3 узлов)"]
S2 -.координация.-> K
Реплики договариваются между собой не напрямую, а через ClickHouse Keeper — он хранит метаданные репликации, очередь задач и обеспечивает консенсус (Raft). Keeper — это встроенная замена ZooKeeper, менее прожорливая и рекомендуемая по умолчанию. Без Keeper ReplicatedMergeTree не работает вовсе.
Идентичность узла в топологии задаётся макросами {shard} и {replica} — они видны в system.macros и подставляются в путь реплики при создании таблицы. Оператор проставляет их за вас.
Сначала вертикаль, потом шардирование
Главная ошибка при переезде в Kubernetes — сразу нарезать много маленьких подов, по аналогии со stateless-сервисами. ClickHouse так не любит: он рассчитан на большие машины и хорошо утилизирует много ядер и памяти в одном процессе. Один узел на 32 ядра и 128 ГиБ памяти на аналитической нагрузке обычно обгоняет четыре узла по 8 ядер и 32 ГиБ — при этом он проще в эксплуатации.
Шардирование добавляет постоянную сложность: распределённые запросы, ключ шардирования, перекосы, ребалансировка при добавлении шарда (её придётся делать руками — автоматического решардинга нет). Поэтому сначала выжимайте вертикаль.
Разумные сигналы, что реплик уже мало и пора в шарды:
- сжатый датасет занимает большую часть диска самого крупного доступного узла;
- устойчивый поток вставки упирается в диск/CPU одного узла;
- время отдельного запроса упирается в CPU одного узла, а не в I/O.
До этих порогов — добавляйте ядра, память и реплики для чтения.
Что меняется именно в Kubernetes
ClickHouse — stateful, и это меняет правила:
- Под не одноразовый. За подом закреплён PVC с данными. Пересоздание пода без своего тома — потеря реплики и долгое восстановление копированием с соседа.
- Планировщик может навредить. Две реплики одного шарда на одной ноде — это не отказоустойчивость. Нужны
podAntiAffinityи распределение по зонам. - Диск решает. Сетевые тома удобны (переезжают за подом), локальные NVMe быстрее, но привязывают под к ноде. Это осознанный компромисс — разберём в части про storage.
- Лимиты памяти жёсткие. Превышение cgroup-лимита — это
OOMKilledбез изящной деградации. ClickHouse нужно явно объяснить, сколько памяти ему можно. - Порядок перезапусков важен. Rolling update без учёта кворума Keeper и здоровья реплик легко превращается в отказ.
Именно поэтому в проде почти всегда используют оператор: он умеет генерировать конфиги, StatefulSet’ы, сервисы, PDB и связывать кластер с Keeper. К операторам переходим в следующей части.
Чего ClickHouse не делает
Чтобы не строить ожиданий, которые он не оправдает:
- это не OLTP-база: точечные
UPDATE/DELETE— дорогие мутации, переписывающие куски; частые изменения строк — не его сценарий; - транзакций в привычном смысле нет;
- JOIN’ы работают, но планировщик не спасёт от плохого запроса: соединение больших таблиц без фильтрации легко упирается в память;
- уникальность строк не гарантируется — дедупликацию проектируют отдельно (
ReplacingMergeTreeи подобные).
Если нагрузка — это аналитика по большим потокам событий, ClickHouse блестящ. Если это «обычная база для приложения» — вы берёте не тот инструмент.
Дальше: Часть 2. Операторы: официальный от ClickHouse Inc. и Altinity.