Партиционирование в ClickHouse: гайд для собеса Data Engineer
Карьерник — Duolingo для аналитиков: 10 минут в день тренируй SQL, Python, A/B, статистику, метрики и ещё 3 темы собеса. 1500+ вопросов в Telegram-боте. Бесплатно.
Содержание:
Зачем партиционирование в ClickHouse
ClickHouse — колоночная СУБД, оптимизированная под аналитические сканы по миллиардам строк. Партиционирование позволяет физически разделить таблицу на куски (partitions), чтобы запрос читал только релевантные. Без партиций — full scan миллиарда строк за каждым SELECT WHERE event_date = today().
Главная боль без партиционирования — деплой простого отчёта «выручка за вчера» забирает 30 секунд на относительно небольшой таблице 100 ГБ. С партиционированием по дню — 200 мс, потому что прочитан один partition.
Это одна из самых частых тем на собесах DE в Яндексе, Авито, X5, Тинькофф — везде, где CH в DWH.
PARTITION BY и parts
CREATE TABLE events (
event_date DATE,
event_time DateTime,
user_id UInt64,
event_type String,
amount Decimal(18, 2)
) ENGINE = MergeTree
PARTITION BY toYYYYMM(event_date)
ORDER BY (event_date, user_id);PARTITION BY toYYYYMM(event_date) — данные физически делятся по месяцам. Каждый месяц = одна или несколько parts на диске.
Part — физический кусок данных в партиции. После INSERT создаётся новый part. Фоновый merge объединяет parts в большие куски, чтобы поиск был быстрее. Партиция — логическая группа parts с одинаковым ключом партиционирования.
Чтобы посмотреть состояние:
SELECT
PARTITION,
count() AS parts_cnt,
sum(ROWS) AS rows_total,
formatReadableSize(sum(bytes)) AS size
FROM system.parts
WHERE TABLE = 'events' AND active
GROUP BY PARTITION
ORDER BY PARTITION;Сколько партиций оптимально: дефолтный гайдлайн — 100–10 000 партиций на таблицу. Помесячное партиционирование на 5 лет = 60 партиций. Подневное — 1825. Подневное на 10 лет = 3650 — тоже норм. Подневное при ретеншене 30 лет — уже избыточно.
Сколько данных в партиции: оптимальный размер part — от 100 МБ до 10 ГБ. Если партиция — 50 МБ, мерж не поможет, IO неэффективный. Если 500 ГБ — OPTIMIZE будет идти часами.
ORDER BY и primary key
В ClickHouse ORDER BY ≠ просто сортировка результата. ORDER BY в MergeTree определяет:
- Физический порядок строк внутри parts — данные сжимаются лучше
- Sparse primary index — индекс на каждые
index_granularity(дефолт 8192) строк - Какие столбцы участвуют в дедупликации (для
ReplacingMergeTree,AggregatingMergeTree)
ENGINE = MergeTree
PARTITION BY toYYYYMM(event_date)
ORDER BY (event_date, user_id, event_type);PRIMARY KEY опционально задаётся отдельно, должен быть префиксом ORDER BY. По умолчанию PRIMARY KEY = ORDER BY. Зачем разделять — если хочешь дедуплицировать по широкому ключу (user_id, event_id, event_type), но индексировать только по (user_id, event_id):
ENGINE = ReplacingMergeTree
PARTITION BY toYYYYMM(event_date)
ORDER BY (user_id, event_id, event_type)
PRIMARY KEY (user_id, event_id);Правило выбора ORDER BY: первые поля — те, по которым чаще всего фильтруют (узкие предикаты), затем по которым джоинят, в конце — высоко-кардинальные (user_id).
Partition pruning
При фильтрации по полю из PARTITION BY ClickHouse читает только релевантные партиции. Это первый уровень оптимизации — гораздо мощнее индекса.
-- читает 1 партицию (один месяц)
SELECT count() FROM events
WHERE event_date BETWEEN '2026-05-01' AND '2026-05-31';
-- читает все партиции (full scan)
SELECT count() FROM events
WHERE user_id = 12345;Pruning не работает, если:
- Фильтр по другому полю (
user_idбезevent_date) - Функция оборачивает поле партиции:
WHERE toDate(toDateTime(event_date)) = '2026-05-01' - Фильтр через подзапрос
WHERE event_date IN (SELECT date FROM other_table)— ClickHouse не может статически вычислить партиции
Проверка: EXPLAIN PIPELINE или EXPLAIN SYNTAX SELECT ... — покажет план. После запуска — system.query_log с read_rows, read_bytes. Если read_rows = всем строкам — pruning не сработал.
Операции с партициями
ClickHouse даёт мощные DDL по партициям:
-- Удалить партицию (мгновенно, не построчно)
ALTER TABLE events DROP PARTITION '202604';
-- Переместить партицию между таблицами
ALTER TABLE events_archive ATTACH PARTITION '202401' FROM events;
ALTER TABLE events DETACH PARTITION '202401';
-- Заморозить (бэкап)
ALTER TABLE events FREEZE PARTITION '202604';
-- Принудительный мерж
OPTIMIZE TABLE events PARTITION '202605' FINAL;DROP PARTITION — самая ценная операция. Удалить день/месяц — мгновенно, без DELETE (которая в CH тяжёлая через ALTER TABLE ... DELETE).
Идемпотентный INSERT через DROP PARTITION + INSERT:
ALTER TABLE events DROP PARTITION '20260501';
INSERT INTO events SELECT * FROM staging WHERE event_date = '2026-05-01';Если партиционирование подневное — это атомарная перезапись дня. Эффективнее ReplacingMergeTree для этого кейса.
Антипаттерны
Партиционирование по высоко-кардинальному полю. PARTITION BY user_id на таблице с 10М юзеров → 10М партиций → ClickHouse ляжет на стартапе таблицы. Кардинальность партиции — десятки, сотни, тысячи. Не миллионы.
Партиционирование по event_time (DateTime). Каждая секунда — новая партиция. Используй toYYYYMM(event_time) или toStartOfWeek(event_time).
ORDER BY (id) для тайм-серий. Если запросы фильтруют по дате — ORDER BY (event_date, ...). С ORDER BY id индекс не поможет на временных запросах.
INSERT по одной строке. Каждый INSERT → новый part → много мерджей → CPU взрывается. Батчить по 10–100k строк.
Бесконтрольные мутации (ALTER ... UPDATE/DELETE). Мутация — это перезапись part целиком. На больших партициях — часы IO. Использовать только в крайнем случае.
Частые ошибки
Партиционирование по дню при ретеншене 10 лет, 1000+ партиций — норм, не баг. ClickHouse спокойно держит 10k партиций. Граница — десятки тысяч и больше.
OPTIMIZE TABLE FINAL в проде. Может перезаписать ВСЕ partitions сразу — на 1 ТБ это часы. Использовать OPTIMIZE TABLE PARTITION '...' FINAL точечно.
Игнорировать system.parts. Если на таблице 5000+ active parts — фоновые мерджи не успевают, INSERT-rate перегнал merge-rate. Признак — растущий count в system.parts.
SELECT FINAL в проде на каждый запрос. FINAL мерджит на лету, замедляет в разы. Использовать argMax/groupBy или периодический OPTIMIZE.
Не задать TTL на партиции. Без TTL старые партиции будут лежать вечно. TTL event_date + INTERVAL 1 YEAR DELETE — авточистка.
Менять PARTITION BY на живой таблице. Нельзя. Только пересоздание таблицы и INSERT INTO new SELECT FROM old.
Связанные темы
- DWH ClickHouse на собесе DE
- Подготовка к собесу Data Engineer
- SQL для Data Engineer: собеседование
- Идемпотентность пайплайна для DE
- OLAP vs OLTP
FAQ
Чем PARTITION BY отличается от ORDER BY в ClickHouse?
PARTITION BY — физическое разделение таблицы на отдельные директории на диске; даёт partition pruning при WHERE-фильтре. ORDER BY — порядок строк внутри parts и sparse primary index. Они работают вместе: партиция фильтрует крупно, ORDER BY ускоряет поиск внутри.
Можно ли партиционировать по нескольким колонкам?
Да: PARTITION BY (toYYYYMM(event_date), country_code). Но осторожно — кардинальность перемножается. 12 месяцев × 200 стран = 2400 партиций. На 5 лет уже 12k. Если фильтр обычно только по дате — лучше один ключ партиционирования.
Как уменьшить количество parts в партиции?
OPTIMIZE TABLE ... PARTITION 'X' FINAL принудительно мерджит все parts партиции в один. Не делать на горячих партициях, идёт долго. Для регулярного — мониторить parts_to_throw_insert и не превышать.
MergeTree vs ReplacingMergeTree — что выбирать?
MergeTree — append-only, дубли остаются. ReplacingMergeTree — дедуплицирует по ORDER BY ключу при мерже, но мерж асинхронный — на чтении дубли могут быть. Для строгой дедупликации использовать argMax, FINAL или OPTIMIZE.
Как правильно делать DELETE в ClickHouse?
Лучший способ — ALTER TABLE ... DROP PARTITION 'X', если данные сгруппированы по партиции. Менее предпочтительно — ALTER TABLE ... DELETE WHERE ... (мутация, переписывает parts). Самый плохой кейс — DELETE без условия по партиции на большой таблице.
Это официальная информация?
Нет. Статья основана на документации ClickHouse и опыте DE-команд. Конкретные настройки (index_granularity, merge_with_ttl_timeout) могут отличаться от ваших.
Тренируйте ClickHouse — откройте тренажёр с 1500+ вопросами для собесов.