Что такое partitioning в PostgreSQL?
Что такое партиционирование в PostgreSQL
Партиционирование (partitioning) — механизм разбиения большой таблицы на несколько меньших физических таблиц, называемых секциями (partitions). С точки зрения приложения работа идёт с единой логической таблицей, а PostgreSQL автоматически маршрутизирует запросы к нужным секциям.
Нативное декларативное партиционирование появилось в PostgreSQL 10 и значительно улучшалось вплоть до версии 14.
Типы партиционирования
Range (по диапазону)
Секции определяются непересекающимися диапазонами значений ключа. Классический пример — партиционирование по дате.
CREATE TABLE orders (
id BIGSERIAL,
created_at TIMESTAMPTZ NOT NULL,
amount NUMERIC
) PARTITION BY RANGE (created_at);
-- Секция для Q1 2025
CREATE TABLE orders_2025_q1
PARTITION OF orders
FOR VALUES FROM ('2025-01-01') TO ('2025-04-01');
-- Секция для Q2 2025
CREATE TABLE orders_2025_q2
PARTITION OF orders
FOR VALUES FROM ('2025-04-01') TO ('2025-07-01');
List (по списку значений)
Секции определяются конкретными значениями ключа.
CREATE TABLE users (
id BIGSERIAL,
region TEXT NOT NULL,
email TEXT
) PARTITION BY LIST (region);
CREATE TABLE users_europe
PARTITION OF users
FOR VALUES IN ('DE', 'FR', 'PL', 'RU');
CREATE TABLE users_america
PARTITION OF users
FOR VALUES IN ('US', 'CA', 'MX');
Hash (по хэшу)
Данные распределяются по секциям на основе хэш-функции от ключа. Используется для равномерного распределения без естественного критерия.
CREATE TABLE events (
id BIGSERIAL,
user_id BIGINT NOT NULL,
payload JSONB
) PARTITION BY HASH (user_id);
CREATE TABLE events_p0
PARTITION OF events
FOR VALUES WITH (MODULUS 4, REMAINDER 0);
CREATE TABLE events_p1
PARTITION OF events
FOR VALUES WITH (MODULUS 4, REMAINDER 1);
Partition Pruning
Ключевое преимущество партиционирования — планировщик может исключить нерелевантные секции из плана выполнения запроса. Это называется partition pruning.
-- PostgreSQL просканирует только секцию orders_2025_q1
EXPLAIN SELECT * FROM orders
WHERE created_at BETWEEN '2025-01-01' AND '2025-03-31';
Управление жизненным циклом данных
Партиционирование радикально упрощает удаление устаревших данных: вместо медленного DELETE можно мгновенно отсоединить секцию.
-- Быстрое удаление целой секции (DROP + detach)
DROP TABLE orders_2024_q1;
-- Или отсоединить секцию без удаления данных
ALTER TABLE orders DETACH PARTITION orders_2024_q1;
Индексы и ограничения на секционированных таблицах
Индекс, созданный на родительской таблице, автоматически создаётся на всех секциях. Primary key и unique constraint обязаны включать ключ партиционирования.
-- Индекс создаётся на всех секциях автоматически
CREATE INDEX ON orders (created_at);
Когда партиционирование помогает
- Таблица содержит сотни миллионов и более строк
- Запросы регулярно фильтруют по ключу партиционирования
- Необходимо регулярно удалять или архивировать старые данные
- Нужно разместить «горячие» и «холодные» данные на разных tablespace
Ограничения
- Уникальные индексы и PK должны включать ключ партиционирования
- Foreign keys на партиционированную таблицу поддерживаются с PostgreSQL 12
- Чрезмерное количество секций увеличивает время планирования запроса
Что хочет услышать интервьюер
Знание трёх типов партиционирования (range, list, hash) и понимание, когда применять каждый
Объяснение механизма partition pruning и его влияния на производительность
Понимание операций жизненного цикла данных: DETACH/ATTACH секций вместо массовых DELETE
Знание ограничений: ключ партиционирования в PK/unique, поведение индексов на дочерних таблицах
Практический опыт: при каком объёме данных партиционирование начинает давать реальный выигрыш
Пример: Range-партиционирование по дате с автоматическим созданием секций
-- Родительская таблица
CREATE TABLE logs (
id BIGSERIAL,
logged_at TIMESTAMPTZ NOT NULL,
level TEXT,
message TEXT
) PARTITION BY RANGE (logged_at);
-- Секция для текущего месяца
CREATE TABLE logs_2025_07
PARTITION OF logs
FOR VALUES FROM ('2025-07-01') TO ('2025-08-01');
-- Секция по умолчанию для данных вне диапазонов
CREATE TABLE logs_default
PARTITION OF logs DEFAULT;
-- Индекс автоматически применяется ко всем секциям
CREATE INDEX ON logs (logged_at DESC);
-- Запрос использует partition pruning — сканируется только logs_2025_07
EXPLAIN (ANALYZE, BUFFERS)
SELECT * FROM logs
WHERE logged_at >= '2025-07-01' AND logged_at < '2025-08-01'
AND level = 'ERROR';
-- Мгновенное удаление старых данных без блокировки таблицы
DROP TABLE logs_2024_07;
Пример: Hash-партиционирование для равномерного распределения нагрузки
-- Таблица событий, распределённых по user_id
CREATE TABLE user_events (
id BIGSERIAL,
user_id BIGINT NOT NULL,
event_type TEXT NOT NULL,
occurred_at TIMESTAMPTZ DEFAULT NOW(),
PRIMARY KEY (id, user_id) -- user_id обязан входить в PK
) PARTITION BY HASH (user_id);
-- Создаём 8 секций для равномерного распределения
DO $$
BEGIN
FOR i IN 0..7 LOOP
EXECUTE format(
'CREATE TABLE user_events_p%s PARTITION OF user_events '
'FOR VALUES WITH (MODULUS 8, REMAINDER %s)',
i, i
);
END LOOP;
END;
$$;
-- Проверяем распределение данных по секциям
SELECT
child.relname AS partition_name,
pg_size_pretty(pg_relation_size(child.oid)) AS size
FROM pg_inherits
JOIN pg_class parent ON pg_inherits.inhparent = parent.oid
JOIN pg_class child ON pg_inherits.inhrelid = child.oid
WHERE parent.relname = 'user_events'
ORDER BY child.relname;
Типичные ошибки
Применение партиционирования к небольшим таблицам — накладные расходы на планирование превысят выгоду
Создание слишком большого числа секций (тысячи), что значительно замедляет планировщик
Попытка создать unique constraint без включения ключа партиционирования
Использование DELETE для очистки старых данных вместо DROP/DETACH секции
Путаница между партиционированием и шардингом: партиционирование работает в рамках одного узла БД


