Опирается на правила:
PG-P-001…PG-P-085из PostgreSQL Style Guide → раздел Партиционирование.
Важно знать
- Партиционирование = одна логическая таблица, несколько физических.
- Главный выигрыш — управляемость на больших объёмах, не «производительность сама по себе».
- Партиционируй если: > 50 GB / 100M строк, time-series, удаление по сроку, autovacuum не справляется.
- Избыточно: < 10 GB, редко в WHERE.
- 3 типа: RANGE (самый частый), LIST, HASH (редко).
- Ключ должен быть в WHERE почти всех запросов.
- Целевой размер партиции 1-50 GB; > 1000 партиций — тормоза.
DROP TABLEпартиции мгновенный vsDELETEминуты + MVCC.- PK обязан включать ключ партиционирования.
- Создание партиций заранее через
pg_partman.
Партиционирование — разделение по правилу (диапазон, список, хэш). UCP формулирует — когда оправдано, как выбрать ключ.
Когда оправдано
PG-P-001..002:
Партиционируй если:
- Таблица > 50 GB (или > 100M строк) и растёт.
- Time-series (события, метрики, логи) — естественное по дате.
- Старые данные удаляются по сроку (
DELETE WHERE created_at < ...мучительный,DROP TABLEмгновенный). - Autovacuum не успевает.
Избыточно:
- < 10 GB.
- Запросы редко с partition key в
WHERE. - Все партиции одного размера и читаются равномерно — это не партиции, это шардирование.
Декларативное партиционирование (PG10+)
CREATE TABLE event_log (
id bigint GENERATED ALWAYS AS IDENTITY,
occurred_at timestamptz NOT NULL,
payload jsonb NOT NULL,
PRIMARY KEY (id, occurred_at) -- PK обязан включать ключ
) PARTITION BY RANGE (occurred_at);
CREATE TABLE event_log_2026_05 PARTITION OF event_log
FOR VALUES FROM ('2026-05-01') TO ('2026-06-01');
CREATE TABLE event_log_2026_06 PARTITION OF event_log
FOR VALUES FROM ('2026-06-01') TO ('2026-07-01');
PG-P-003: PK обязан включать ключ партиционирования. Поэтому идиоматично: PRIMARY KEY (id, occurred_at).
-- INSERT идёт в родительскую, PG раскладывает по партициям
INSERT INTO event_log (occurred_at, payload) VALUES (now(), '{"k":"v"}');
-- SELECT с partition pruning
SELECT * FROM event_log
WHERE occurred_at >= '2026-05-15' AND occurred_at < '2026-05-20';
-- читается ТОЛЬКО event_log_2026_05
Три типа
PG-P-010..013:
RANGE — самый частый
PARTITION BY RANGE (occurred_at);
Для time-series, архивов по месяцу/году, числовых шкал.
LIST
CREATE TABLE order_doc (...) PARTITION BY LIST (region);
CREATE TABLE order_doc_eu PARTITION OF order_doc FOR VALUES IN ('EU', 'UK');
CREATE TABLE order_doc_usa PARTITION OF order_doc FOR VALUES IN ('US', 'CA');
CREATE TABLE order_doc_other PARTITION OF order_doc DEFAULT;
Когда фиксированный набор значений-категорий (regions, tenants, типы).
HASH
PARTITION BY HASH (user_id);
CREATE TABLE user_event_p0 PARTITION OF user_event FOR VALUES WITH (MODULUS 4, REMAINDER 0);
CREATE TABLE user_event_p1 PARTITION OF user_event FOR VALUES WITH (MODULUS 4, REMAINDER 1);
PG-P-013: HASH оправдан редко. Не даёт partition pruning по диапазону. Помогает только при WHERE user_id = ? (точечно).
Выбор ключа
PG-P-020..022: самое важное решение.
Ключ должен быть в WHERE почти всех запросов.
Правильно:
- Time-series:
occurred_at. Все запросыWHERE occurred_at > ?. - Multi-tenant:
tenant_id. Каждый запрос в контексте одного тенанта. - Архив заказов:
created_at.
Неправильно:
- Партиционировать
ordersпоstatus, если запросы поcustomer_id. - Партиционировать
customerпоcountry, если 90% работы вRU.
Распределение нагрузки: партиции сравнимого размера. Если 99% — одна партиция, остальные пустые — это не партиционирование.
SELECT date_trunc('month', occurred_at) AS m, count(*)
FROM event_log GROUP BY m ORDER BY m;
Размер партиции
PG-P-030..031:
Целевой: 1-50 GB.
Для time-series:
- Помесячно — миллионы строк/месяц, 1-20 GB.
- Понедельно — десятки миллионов/неделю.
- Подневно — сотни миллионов/день.
1000 партиций — планировщик тормозит. > 10000 — кластер еле дышит.
Управление
PG-P-040..042: создание заранее, удаление через DROP.
Создание партиций заранее
CREATE TABLE event_log_2026_07 PARTITION OF event_log
FOR VALUES FROM ('2026-07-01') TO ('2026-08-01');
Если первая запись месяца попадёт без партиции — INSERT упадёт. Заводи на 1-2 месяца вперёд.
Автоматически — pg_partman:
CREATE EXTENSION pg_partman;
SELECT partman.create_parent(
p_parent_table => 'public.event_log',
p_control => 'occurred_at',
p_type => 'native',
p_interval => '1 month',
p_premake => 4
);
-- cron:
SELECT partman.run_maintenance('public.event_log');
DROP TABLE партиции
PG-P-041: главный практический выигрыш.
DROP TABLE event_log_2025_05; -- мгновенно
vs DELETE FROM event_log WHERE occurred_at < ...:
- Сначала seq scan миллионов строк.
- MVCC-маркеры.
- Vacuum для освобождения места.
- Десятки минут с активным WAL и replication lag.
DETACH PARTITION
PG-P-042: отделить без удаления.
ALTER TABLE event_log DETACH PARTITION event_log_2025_05;
-- теперь event_log_2025_05 — обычная таблица
С DETACH ... CONCURRENTLY (PG14+) — без блокировки родительской.
Индексы
PG-P-050..051:
CREATE INDEX на родительской автоматически создаёт на каждой партиции:
CREATE INDEX ON event_log (payload->>'event_type');
-- создастся на event_log_2026_05, event_log_2026_06, ...
Уникальные индексы должны включать ключ партиционирования. Уникальность гарантируется только в пределах партиции — для глобальной по UUID отдельная таблица справочника.
Foreign Keys
PG-P-060..061:
- Partitioned → обычная: FK работает.
- Обычная → partitioned: с PG12+ работает, требует PK с partition key.
Миграция в партиции
PG-P-070..071: через теневую таблицу.
-- 1. новая partitioned
CREATE TABLE event_log_new (LIKE event_log INCLUDING ALL)
PARTITION BY RANGE (occurred_at);
-- партиции...
-- 2. копирование
INSERT INTO event_log_new SELECT * FROM event_log;
-- 3. swap
BEGIN;
ALTER TABLE event_log RENAME TO event_log_old;
ALTER TABLE event_log_new RENAME TO event_log;
COMMIT;
DROP TABLE event_log_old;
На 500 GB — несколько часов копирования + место под обе версии.
Что запрещено
| Антипаттерн | Правило | Что взамен |
|---|---|---|
| Партиционирование < 10 GB | PG-P-080 | обычные индексы + vacuum |
Ключ не в WHERE | PG-P-081 | другой ключ или нет партиций |
| Per-tenant партиции для тысяч | PG-P-082 | row-per-tenant + RLS |
| > 1000 партиций (по часам) | PG-P-083 | помесячно/понедельно |
| Не создан партиция заранее | PG-P-084 | pg_partman |
UPDATE поля партиционирования | PG-P-085 | immutable partition key |
DELETE старых данных | PG-P-041 | DROP TABLE партиции |
| PK без partition key | PG-P-003 | PRIMARY KEY (id, occurred_at) |
Куда дальше
- PG → Партиционирование — нормативные формулировки.
- Multi-tenancy — row-per-tenant vs партиции.
- Materialized views — альтернатива для агрегатов.
- Расширения —
pg_partman. - VACUUM — autovacuum на партициях.
- Миграции без даунтайма — миграция в партиции.