Каковы стратегии партиционирования таблиц в PostgreSQL (range, list, hash)?
PostgreSQL поддерживает три стратегии: RANGE (диапазоны, типично по времени), LIST (дискретные категории) и HASH (равномерное распределение по N секциям). Partition pruning работает только когда WHERE содержит ключ партиционирования.
Зачем нужно партиционирование
Партиционирование делит одну логическую таблицу на физически отдельные «секции» (partitions). PostgreSQL поддерживает декларативное партиционирование с версии 10. Основные выгоды: partition pruning (планировщик сканирует только релевантные секции), параллельный scan, быстрое удаление старых данных через DROP PARTITION вместо DELETE.
RANGE — партиционирование по диапазону
Используется для временны́х данных (логи, события, заказы по дате). Планировщик автоматически исключает секции вне запрошенного диапазона.
CREATE TABLE events (
id BIGSERIAL,
created_at TIMESTAMPTZ NOT NULL,
user_id BIGINT,
payload JSONB
) PARTITION BY RANGE (created_at);
-- Создание секций вручную
CREATE TABLE events_2024_01
PARTITION OF events
FOR VALUES FROM ('2024-01-01') TO ('2024-02-01');
CREATE TABLE events_2024_02
PARTITION OF events
FOR VALUES FROM ('2024-02-01') TO ('2024-03-01');
-- Секция для «всего остального» (DEFAULT)
CREATE TABLE events_default
PARTITION OF events DEFAULT;
-- Partition pruning в действии
EXPLAIN SELECT * FROM events WHERE created_at >= '2024-01-15';
-- Partitions: Seq Scan on events_2024_01, events_2024_02... (остальные исключены)
LIST — партиционирование по списку значений
Используется для дискретных категорий: регион, статус, тип объекта. Не подходит для полей с высокой кардинальностью.
CREATE TABLE orders (
id BIGSERIAL,
region TEXT NOT NULL,
amount NUMERIC
) PARTITION BY LIST (region);
CREATE TABLE orders_eu
PARTITION OF orders
FOR VALUES IN ('DE', 'FR', 'NL', 'PL');
CREATE TABLE orders_us
PARTITION OF orders
FOR VALUES IN ('US', 'CA');
CREATE TABLE orders_apac
PARTITION OF orders
FOR VALUES IN ('JP', 'SG', 'AU');
CREATE TABLE orders_other
PARTITION OF orders DEFAULT;
HASH — партиционирование по хешу
Равномерно распределяет строки по N секциям. Используется когда нет естественного диапазона или списка, но хочется уменьшить размер каждой секции (например, для параллельного vacuum или разноса по разным tablespace).
CREATE TABLE user_activity (
id BIGSERIAL,
user_id BIGINT NOT NULL,
data JSONB
) PARTITION BY HASH (user_id);
-- 4 равных секции
CREATE TABLE user_activity_p0
PARTITION OF user_activity
FOR VALUES WITH (MODULUS 4, REMAINDER 0);
CREATE TABLE user_activity_p1
PARTITION OF user_activity
FOR VALUES WITH (MODULUS 4, REMAINDER 1);
CREATE TABLE user_activity_p2
PARTITION OF user_activity
FOR VALUES WITH (MODULUS 4, REMAINDER 2);
CREATE TABLE user_activity_p3
PARTITION OF user_activity
FOR VALUES WITH (MODULUS 4, REMAINDER 3);
Индексы на партиционированных таблицах
-- Глобальный индекс создаётся автоматически на всех секциях
CREATE INDEX idx_events_user_id ON events (user_id);
-- PRIMARY KEY и UNIQUE должны включать ключ партиционирования
ALTER TABLE events ADD PRIMARY KEY (id, created_at);
Автоматическое создание секций
Для RANGE по времени используют pg_partman — расширение для автоматического создания и удаления секций по расписанию.
-- После установки pg_partman
SELECT partman.create_parent(
p_parent_table := 'public.events',
p_control := 'created_at',
p_type := 'native',
p_interval := '1 month',
p_premake := 3 -- создать 3 секции вперёд
);
Подводные камни
- Partition key в WHERE обязателен для pruning. Если запрос не фильтрует по ключу партиционирования, PostgreSQL сканирует все секции. EXPLAIN покажет «Append» со всеми дочерними узлами.
- PRIMARY KEY / UNIQUE должны включать ключ партиционирования. Это ограничение архитектуры декларативного партиционирования в PostgreSQL — глобальных уникальных индексов нет.
- Внешние ключи на партиционированную таблицу не поддерживаются (FK из другой таблицы, ссылающийся на партиционированную). Решение — триггеры или денормализация.
- Слишком много секций замедляет planning time. При 1000+ секций планировщик тратит значительное время на pruning. Оптимум — десятки, максимум сотня секций.
- DROP TABLE partition vs DETACH + DROP. DETACH PARTITION позволяет «открепить» секцию и работать с ней как с обычной таблицей. С PG 14 есть DETACH CONCURRENTLY без долгой блокировки.
- Bloat в DEFAULT-секции. Если данные часто попадают в DEFAULT, а потом новые секции создаются вручную, DEFAULT разрастается. Используйте pg_partman для автоматизации.
- Миграция существующей таблицы. Нельзя конвертировать обычную таблицу в партиционированную на месте. Нужно создать новую, перелить данные, переименовать.
Common mistakes
- Отвечать про range/list/hash partitioning как про абстрактный SQL без учета MVCC, WAL, planner или конкретной версии PostgreSQL.
- Переносить поведение MySQL или ClickHouse на PostgreSQL без проверки документации.
- Не связывать механизм с последствиями для индексов, транзакций, блокировок или эксплуатации.
- Показывать синтаксис, который не поддерживается PostgreSQL.
What the interviewer is testing
- Кандидат объясняет range/list/hash partitioning через реальный механизм PostgreSQL, а не общими словами.
- Приводит корректный SQL или диагностический запрос для этой СУБД.
- Называет ограничения, версионные отличия или эксплуатационные последствия.
- Связывает ответ с проектированием приложения, производительностью, надежностью или безопасностью.