Логи веб-серверов, события приложений, клики и метрики копятся со скоростью тысячи строк в секунду, и обычная построчная база на таком потоке задыхается: ради одного счётчика она читает с диска миллионы строк целиком. Колоночная СУБД ClickHouse переворачивает подход к хранению: значения одной колонки лежат рядом, сжимаются единым блоком и читаются за один проход, поэтому отчёт по миллиону событий на скромном сервере укладывается в доли секунды. Ставится она на Linux за несколько минут, и уже через десять можно завести первую таблицу на движке MergeTree, залить в неё данные и погонять живые запросы. Ниже весь путь: репозиторий и пакеты, проверка сервера, устройство таблицы, вставка данных и аналитические SELECT, с которых начинается любая работа с событиями.
Установка ClickHouse на Debian и Ubuntu из официального репозитория
Для быстрого эксперимента хватает однострочника: скрипт скачивает одиночный бинарник и утилиту clickhousectl, которая помогает держать несколько локальных версий и запускать сервер в фоне.
curl https://clickhouse.com/ | sh
./clickhouse server
Запущенный так сервер хранит данные в текущем каталоге и годится для песочницы. Серверу на постоянную работу нужен пакет: служба под systemd, конфигурация в /etc, логи по стандартным путям, обновления через apt. Схема знакома каждому, кто собирал сторонний репозиторий вручную.
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
ARCH=$(dpkg --print-architecture)
echo "deb [signed-by=/usr/share/keyrings/clickhouse-keyring.gpg arch=${ARCH}] 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
Установщик спросит пароль пользователя default, и задать его лучше сразу. В репозитории два канала: stable обновляется часто и получает свежие исправления, lts меняется редко и живёт дольше. Осенью 2026 актуальная стабильная ветка - 26.9, её свежий релиз вышел в первых числах октября. Если версию надо зафиксировать, все пакеты ставят одной цифрой, иначе сервер и клиент со временем разойдутся по возможностям.
apt-cache policy clickhouse-server
sudo apt-get install clickhouse-server=26.9.13.15 clickhouse-client=26.9.13.15 clickhouse-common-static=26.9.13.15
Пара цифр для планирования: бинарнику нужно минимум 2,5 ГБ на диске, поток кликов после сжатия обычно занимает в 6-10 раз меньше исходного объёма, а для кластеров рекомендуют сеть от 10 Гбит. В продакшене советуют отключить подкачку: при нехватке памяти серверу полезнее быстро упасть и подняться наблюдателем, чем медленно вязнуть в свопе. Отдельный пакет clickhouse-keeper ставят только на выделенные узлы координации, одиночному серверу он не нужен.
Проверка сервера и учётные записи в конфигах
Службу включают и сразу смотрят статус:
sudo systemctl enable --now clickhouse-server
sudo systemctl status clickhouse-server
Клиент подключается к localhost:9000 под пользователем default, пароль для него спросит флаг --password. Первый осмотр выглядит так:
SELECT version();
SHOW DATABASES;
Конфигурация лежит в /etc/clickhouse-server: config.xml описывает порты, пути хранения и журналы, users.xml и каталог users.d управляют учётками, профилями и квотами. Нативный протокол слушает порт 9000, HTTP API - порт 8123, его удобно проверять curl:
curl -s 'http://localhost:8123/' --data-binary 'SELECT 1'
Для аналитиков принято заводить отдельную учётку с правами только на чтение, чтобы любопытный дашборд не мог удалить таблицу парой кликов:
CREATE DATABASE IF NOT EXISTS analytics;
CREATE USER analyst IDENTIFIED BY 'Ns8vXq92pz';
GRANT SELECT ON analytics.* TO analyst;
Журналы сервера лежат в /var/log/clickhouse-server: clickhouse-server.log собирает обычные события, clickhouse-server.err.log - ошибки и трассировки. Памятью стоит заняться сразу: настройка max_server_memory_usage_to_ram_ratio ограничивает аппетит сервера долей оперативной памяти, и разумная практика - оставлять системе 10-15 процентов, чтобы тяжёлый запрос не вынудил ядро завершить процесс сервера.
Устройство MergeTree и создание первой таблицы с ключами
MergeTree - базовый движок семейства, рассчитанный на огромные объёмы и высокий темп вставки. Каждая вставка создаёт на диске неизменяемый кусок данных, часть (part), а фоновые потоки сливают части между собой, не давая им плодиться без меры. Порядок строк внутри части задаёт ключ сортировки ORDER BY: одинаковые значения оказываются рядом, а запросы по диапазону читают подряд идущие блоки. Индекс у MergeTree разреженный: одна запись ключа приходится на гранулу из 8192 строк при умолчании index_granularity 8192, поэтому ключ по миллиардной таблице помещается в памяти целиком.
Партиции задаются выражением PARTITION BY, чаще всего помесячно: toYYYYMM(event_date) даёт идентификаторы вида 202610. Когда в WHERE фигурирует дата, сервер целиком пропускает ненужные месяцы, это называют отсечением партиций. Важно не переусердствовать: партиции сами по себе не ускоряют запросы, за скорость отвечает сортировка, а лишняя мелкая нарезка лишь плодит каталоги и слияния. Партиционировать по идентификатору пользователя не стоит, его место в ORDER BY. Если сортировка не нужна вовсе, пишут ORDER BY tuple().
CREATE TABLE analytics.events
(
event_date Date,
user_id UInt32,
url String,
bytes UInt64,
response_ms UInt32,
status UInt8
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(event_date)
ORDER BY (event_date, user_id);
После первой вставки полезно заглянуть в служебную таблицу:
SELECT partition, name, rows, bytes_on_disk
FROM system.parts
WHERE table = 'events';
Части лежат под /var/lib/clickhouse/data/analytics/events/ в каталогах с именами вроде all_1_1_0, внутри них данные и служебные текстовые файлы вроде count.txt и columns.txt. По мере слияний старые части исчезают из system.parts, а столбец active помечает живые. Привычка поглядывать туда экономит нервы: всплеск числа частей виден раньше, чем ошибка вставки в логах.
Загрузка данных в таблицу и генерация тестового потока
Простейшая вставка похожа на привычную по другим СУБД:
INSERT INTO analytics.events VALUES
(today(), 42, '/', 1200, 95, 200),
(today(), 43, '/catalog', 6400, 120, 200);
Главное правило MergeTree: вставлять редко и крупными пакетами. Каждая операция INSERT рождает новую часть, и поток одиночных строк быстро упирается в ошибку "Too many parts". Комфортный ритм - от пачки в секунду до пачки в минуту, размером от тысяч до миллиона строк.
Миллион тестовых строк генерируется прямо в клиенте:
INSERT INTO analytics.events
SELECT
today() - rand() % 90 AS event_date,
rand() % 100000 AS user_id,
['/','/catalog','/cart'][rand() % 3 + 1] AS url,
rand() % 100000 AS bytes,
rand() % 500 AS response_ms,
if(rand() % 20 = 0, 500, 200) AS status
FROM numbers(1000000);
Табличная функция numbers() возвращает миллион пронумерованных строк, а выражения на rand() наполняют колонки правдоподобным разбросом. Файлы принимаются так же просто:
cat events.json | clickhouse-client --password --query="INSERT INTO analytics.events FORMAT JSONEachRow"
Форматов ввода десятки: JSONEachRow, CSV, TSV и другие, так что логи почти всегда получается грузить без промежуточных конвертаций. Кривой входной файл сильнее любых настроек влияет на здоровье таблицы: одна сломанная строка в JSON способна остановить всю пачку, и спасает параметр input_format_skip_unknown_fields, разрешающий игнорировать лишние поля.
Базовые SELECT запросы для аналитики событий и логов
Набор запросов, с которого начинается любая разведка данных:
SELECT count() FROM analytics.events;
SELECT uniq(user_id) AS users
FROM analytics.events
WHERE event_date >= today() - 7;
SELECT url, count() AS hits,
round(avg(response_ms), 1) AS avg_ms
FROM analytics.events
WHERE event_date >= today() - 7
GROUP BY url
ORDER BY hits DESC
LIMIT 10;
SELECT quantile(0.95)(response_ms) AS p95
FROM analytics.events
WHERE status != 200;
uniq считает уникальных пользователей приближённо и потому почти не тратит память; точный вариант uniqExact существует, но на тяжёлых логах он дорог. quantile оценивает перцентиль ответа приближённо, точный аналог quantileExact работает дольше. Ускорение достигается самой формой хранения: для последнего запроса сервер читает только колонки response_ms и status, остальные столбцы остаются на диске нетронутыми. WHERE по дате здесь не прихоть, а экономия: он отсекает целые месяцы партиций. Для скриптов ответ приводят к компактному виду:
SELECT count() FROM analytics.events FORMAT TSV
Смежные движки MergeTree для дедупликации и агрегатов
У семейства есть специализированные наследники, и выбирать их стоит до того, как таблица обросла данными. ReplacingMergeTree с колонкой-версией в параметре движка хранит лишь самую свежую строку по ключу сортировки; слияния происходят в фоне в непредсказуемый момент, поэтому в запросах применяют FINAL либо GROUP BY с argMax. SummingMergeTree суммирует числовые колонки при совпадении ключа, удобный вариант для готовых счётчиков метрик. AggregatingMergeTree хранит промежуточные состояния агрегатов, из которых потом собирают отчёты по периодам, а CollapsingMergeTree с колонкой-признаком отслеживает изменяемые сущности, скручивая старые версии строк.
Классическая связка - материализованное представление поверх сырых событий, наполняющее витрину на SummingMergeTree:
CREATE TABLE analytics.url_stats
(
event_date Date,
url String,
hits UInt64
)
ENGINE = SummingMergeTree
ORDER BY (event_date, url);
CREATE MATERIALIZED VIEW analytics.url_stats_mv
TO analytics.url_stats
AS
SELECT event_date, url, count() AS hits
FROM analytics.events
GROUP BY event_date, url;
Каждая вставка в events автоматически пополняет витрину, и отчёт по ней читает тысячи готовых строк вместо миллионов исходных. Время жизни строк задаёт TTL:
ALTER TABLE analytics.events MODIFY TTL event_date + INTERVAL 90 DAY;
Устаревшие строки удаляются сами, фоновыми слияниями. Разовое удаление делает лёгкий синтаксис:
DELETE FROM analytics.events WHERE event_date < today() - 120;
Тяжёлые массовые правки идут через ALTER TABLE ... DELETE, асинхронные мутации видны в system.mutations. Принудительное слияние командой OPTIMIZE TABLE events FINAL экономит место после больших чисток, но создаёт заметную нагрузку, поэтому его не запускают в часы пик.
Ошибки первых недель и порядок шагов новичка
Типовые грабли предсказуемы. Потерянный пароль default чинится правкой users.xml и перезапуском службы, а лучше сразу класть пароль в хранилище секретов. Версии clickhouse-client и clickhouse-server из разных веток порой не понимают друг друга, вот почему пакеты фиксируют в одной цифре. Куст однострочных вставок оборачивается ошибкой "Too many parts", и лечится он не настройками, а переписыванием конвейера на пачки. Отчёт без даты в WHERE перечитывает таблицу целиком, и никакая сортировка не спасает. Память лимитируется настройкой max_memory_usage, а при тяжёлых агрегатах помогает выгрузка промежуточных результатов на диск через max_bytes_before_external_group_by.
Скоро после установки стоит пройтись по короткому чек-листу:
- включить ветку репозитория stable или lts и ставить все пакеты одной версией;
- задать пароль default и завести аналитикам учётки с правами только на чтение;
- вставлять данные крупными пачками, следя за числом частей в system.parts;
- класть дату в WHERE, чтобы сервер пропускал ненужные месяцы;
- перед удалением или перестройкой таблицы сверяться с копией, ведь DROP TABLE срабатывает мгновенно и безвозвратно.
Первая таблица на MergeTree и есть та точка, где песочница становится рабочей аналитикой. Дальше по той же колее ставят реплицированный вариант движка с координацией через Keeper, заводят материализованные представления для предагрегатов и подключают конвейер доставки данных. А логи, годами копившиеся на сервере мёртвым грузом, наконец начинают отвечать на вопросы.