PARTITION BY: жизненный цикл, не оптимизация запросов
Одно из самых частых заблуждений при проектировании ClickHouse таблиц — использование PARTITION BY для ускорения запросов. Официальная документация ClickHouse прямо предупреждает:
“Partitioning does not speed up queries (in contrast to the ORDER BY expression)”
PARTITION BY определяет, как данные группируются в отдельные директории на диске. Каждая партиция содержит свои parts, которые никогда не объединяются с parts из других партиций. Это свойство делает партиции инструментом управления жизненным циклом данных, а не оптимизации запросов.
Зачем нужен PARTITION BY
PARTITION BY решает три задачи:
1. TTL и автоматическое удаление. ClickHouse удаляет данные по TTL целыми партициями. Если партиция = месяц, при истечении TTL ClickHouse дропает целую директорию, а не ищет и удаляет отдельные строки. Это мгновенная операция.
2. DETACH / ATTACH для архивирования. Партицию можно отключить от таблицы (DETACH) без удаления файлов. Позже её можно подключить обратно (ATTACH) или перенести на другой сервер. Это позволяет перемещать исторические данные на дешёвое хранилище.
3. REPLACE для bulk-обновлений. ALTER TABLE REPLACE PARTITION атомарно заменяет партицию новыми данными. Это паттерн для перезагрузки данных из внешних источников: подготовить данные во временной таблице, затем заменить партицию.
Правила размеров
| Метрика | Рекомендация |
|---|---|
| Размер одной партиции | 1-300 ГБ |
| Общее число партиций | Десятки или сотни (не тысячи) |
| Максимум | Менее 1000 партиций |
Типичные паттерны
-- Ежемесячное партиционирование (стандарт)
-- ~12 партиций в год, каждая от нескольких ГБ до сотен ГБ
PARTITION BY toYYYYMM(event_date)
-- Ежегодное партиционирование (длительное хранение)
-- Для данных, хранимых годами с редким удалением
PARTITION BY toYear(event_date)
-- Ежедневное партиционирование (высокий объём + короткий retention)
-- 100+ ГБ в день, retention 30-90 дней
PARTITION BY toDate(event_date)
Для небольших таблиц (менее 100 миллионов строк) опустите PARTITION BY полностью — все данные будут в одной партиции. Это нормально и даже предпочтительно: меньше overhead на управление.
Anti-pattern: over-partitioning
Over-partitioning — это создание слишком большого числа партиций. Каждый INSERT создаёт parts в каждой затронутой партиции. Фоновый merge объединяет parts только внутри одной партиции. Если партиций тысячи, merge не успевает за INSERT, и число parts растёт бесконтрольно.
Пример: PARTITION BY user_id при миллионе пользователей:
- Каждый INSERT может затрагивать тысячи user_id = тысячи маленьких parts
- Merge не успевает: parts накапливаются
- При достижении
parts_to_throw_insert(по умолчанию 300 parts на партицию) ClickHouse отклоняет INSERT
-- Anti-pattern: не делайте так
CREATE TABLE user_events (
user_id UInt64,
event_date Date,
data String
) ENGINE = MergeTree()
PARTITION BY user_id -- МИЛЛИОНЫ партиций!
ORDER BY (event_date)
Более 1000 партиций — сигнал проблемы. Проверяйте текущее состояние:
SELECT partition, count() AS parts
FROM system.parts
WHERE table = 'events' AND active = 1
GROUP BY partition
ORDER BY parts DESC
LIMIT 10Partition pruning — бонус, не цель
ClickHouse выполняет partition pruning: если запрос содержит условие на столбец PARTITION BY, ClickHouse пропускает партиции, не соответствующие условию. Это ускоряет запросы, но:
- Partition pruning — грубый (целые месяцы/дни), а не гранулярный как ORDER BY pruning
- ORDER BY pruning работает внутри каждой партиции и всегда эффективнее
- Проектировать PARTITION BY ради partition pruning — ошибка: выбирайте PARTITION BY по lifecycle needs, а ORDER BY — по query needs
Ключевые выводы
- PARTITION BY — инструмент жизненного цикла: TTL удаление, DETACH/ATTACH архивирование, REPLACE для bulk-обновлений. Не инструмент оптимизации запросов.
- Размер партиции: 1-300 ГБ, общее число: десятки или сотни. Менее 1000 партиций.
- toYYYYMM(event_date) — стандартный выбор для большинства таблиц с временными данными.
- Маленькие таблицы — опустите PARTITION BY полностью. Одна партиция — это нормально.
- Over-partitioning (PARTITION BY user_id при миллионах пользователей) приводит к ошибке “Too many parts” и отказу INSERT.