ClickHouse — колоночная СУБД для аналитики: она быстро считает агрегаты по миллиардам строк, но не предназначена для транзакционной нагрузки и точечных обновлений. Типичный сценарий — хранить события и метрики и отвечать на вопросы вида «сколько уникальных пользователей было вчера по региону». В статье разберём установку, модель данных на MergeTree, загрузку и обслуживание.
Где ClickHouse уместен, а где нет
База оптимизирована под последовательное чтение больших блоков и сжатие колонок. Запрос SELECT count() FROM events WHERE date = today() по сотням миллионов строк здесь — обычное дело. А вот UPDATE одной строки по первичному ключу — дорогая операция, которая выполняется асинхронной мутацией. Отсюда правило: ClickHouse ставят рядом с источником событий, а не вместо PostgreSQL для заказов.
- Подходит: веб-аналитика, логи, метрики, финансовые отчёты, OLAP-витрины.
- Не подходит: очереди, сессии, корзина интернет-магазина, частые построчные правки.
- Хорошо сочетается: PostgreSQL для операционных данных плюс ClickHouse для аналитики.
Установка из официального репозитория
Для Debian и Ubuntu надёжнее официальный репозиторий: он даёт свежие пакеты и обновления безопасности. Команды выполняются от пользователя с sudo. Ключ и список источников нужно добавить один раз.
sudo apt-get install -y apt-transport-https ca-certificates curl gnupg
curl -fsSL https://packages.clickhouse.com/rpm/lts/repodata/repomd.xml.key \
| sudo gpg --dearmor -o /usr/share/keyrings/clickhouse-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/clickhouse-keyring.gpg] https://packages.clickhouse.com/deb stable main" \
| sudo tee /etc/apt/sources.list.d/clickhouse.list
sudo apt-get update
sudo apt-get install -y clickhouse-server clickhouse-client
sudo systemctl enable --now clickhouse-server
Первый запуск и клиент
После установки сервер слушает локальный порт и принимает подключения пользователя default без пароля. На сервере, доступном из сети, это недопустимо: сразу ограничьте прослушивание и задайте пароль.
clickhouse-client --query "SELECT version()"
clickhouse-client --query "SELECT name, value FROM system.settings WHERE name = 'max_memory_usage'"
sudo ss -ltnp | grep clickhouse
В /etc/clickhouse-server/config.xml оставьте в listen_host только 127.0.0.1 и ::1, если внешний доступ не нужен. Пользователей опишите в users.xml, задав каждому max_memory_usage.
Модель данных: MergeTree
Основной движок — MergeTree. Данные хранятся в партах, отсортированных по ключу ORDER BY, а партиции по PARTITION BY позволяют удалять старые данные целиком. Ключ сортировки — самый важный выбор: по нему строится разреженный первичный индекс, и он определяет, сколько гранул придётся прочитать.
CREATE DATABASE IF NOT EXISTS logs;
CREATE TABLE logs.events
(
ts DateTime,
service LowCardinality(String),
level LowCardinality(String),
user_id UInt64,
message String,
duration_ms UInt32
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(ts)
ORDER BY (service, ts, user_id)
TTL ts + INTERVAL 180 DAY
SETTINGS index_granularity = 8192;
LowCardinality(String) заметно экономит место на колонках с небольшим числом уникальных значений. Партиции по месяцу — разумный компромисс: слишком мелкое дробление создаёт тысячи партов, слишком крупное мешает удалять старое.
Загрузка данных
Вставляйте крупными батчами: одна вставка на строку убивает производительность. Для файлов используйте нативный формат или CSV с явной схемой. Проверьте, что порядок колонок совпадает.
clickhouse-client --query "INSERT INTO logs.events FORMAT JSONEachRow" < events.ndjson
clickhouse-client --query "INSERT INTO logs.events SELECT * FROM file('events.csv', CSVWithNames)"
clickhouse-client --query "SELECT count(), uniqExact(user_id) FROM logs.events"
clickhouse-client --query "SELECT service, quantile(0.95)(duration_ms) AS p95 FROM logs.events GROUP BY service ORDER BY p95 DESC"
Приложения отправляют пачки раз в несколько секунд: мелкие вставки создают лишние парты. Следите за ними через system.parts и system.merges.
Агрегаты и материализованные представления
Аналитические запросы в ClickHouse пишутся почти как в обычном SQL, но с функциями для приблизительных и точных агрегатов: uniq, uniqExact, quantile, topK.
Если один и тот же тяжёлый агрегат нужен постоянно, постройте материализованное представление на AggregatingMergeTree. Оно хранит промежуточные состояния, а не готовые числа, и объединяет их при чтении.
CREATE MATERIALIZED VIEW logs.events_hourly
ENGINE = AggregatingMergeTree
PARTITION BY toYYYYMM(hour)
ORDER BY (service, hour)
AS SELECT
service,
toStartOfHour(ts) AS hour,
countState() AS requests,
uniqState(user_id) AS users,
quantileState(0.95)(duration_ms) AS p95
FROM logs.events
GROUP BY service, hour;
Читать такое представление нужно через countMerge, uniqMerge и quantileMerge. Обычный SELECT requests вернёт бинарное состояние, а не число — это самая частая ошибка новичков.
Обслуживание: парты, мутации и TTL
TTL автоматически удаляет устаревшие данные во время фоновых слияний. Удаление строк через ALTER TABLE ... DELETE создаёт мутацию, которая переписывает парты и может идти часами. Для массовой очистки лучше удалять целые партиции.
SELECT partition, rows, formatReadableSize(bytes_on_disk) AS size
FROM system.parts
WHERE table = 'events' AND active
ORDER BY partition DESC;
ALTER TABLE logs.events DROP PARTITION '202501';
SELECT * FROM system.mutations WHERE is_done = 0;
OPTIMIZE TABLE logs.events FINAL;
OPTIMIZE ... FINAL схлопывает парты и применяет TTL, но на большой таблице это тяжёлая операция. Проверяйте system.mutations, если ALTER завис.
Резервное копирование
Встроенная команда BACKUP умеет писать на локальный диск или в S3. Для неё в конфигурации должен быть описан диск назначения.
BACKUP TABLE logs.events TO Disk('backups', 'events.zip');
RESTORE TABLE logs.events FROM Disk('backups', 'events.zip');
SELECT * FROM system.backups ORDER BY start_time DESC LIMIT 5;
Для больших кластеров удобнее clickhouse-backup: он умеет инкрементальные копии и загрузку в объектное хранилище.
Диагностика и настройка
При проблемах смотрите system.query_log (медленные запросы), system.processes (что выполняется сейчас), system.merges и system.replicas для кластера. Ограничения памяти задаются на пользователя, а не глобально, поэтому один тяжёлый отчёт не должен ронять остальные.
- Ставьте
max_memory_usageиmax_execution_timeдля пользователей дашбордов. - Не выбирайте
SELECT *из широких таблиц: колоночное чтение экономит только при явном списке полей. - Фильтруйте по ключу сортировки, иначе индекс не поможет.
- Следите за количеством активных партов: сотни на партицию — повод разобраться.
Итог
ClickHouse — не универсальная база, а инструмент под конкретную задачу: быстрый анализ больших объёмов событий. Начните с одной таблицы MergeTree, продуманного ORDER BY и месячных партиций, загружайте данные батчами и проверяйте план через system.query_log. Материализованные представления добавляйте только после того, как увидели реально медленный запрос.