Опирается на правила: PG-P-001PG-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 партиции мгновенный vs DELETE минуты + 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 GBPG-P-080обычные индексы + vacuum
Ключ не в WHEREPG-P-081другой ключ или нет партиций
Per-tenant партиции для тысячPG-P-082row-per-tenant + RLS
> 1000 партиций (по часам)PG-P-083помесячно/понедельно
Не создан партиция заранееPG-P-084pg_partman
UPDATE поля партиционированияPG-P-085immutable partition key
DELETE старых данныхPG-P-041DROP TABLE партиции
PK без partition keyPG-P-003PRIMARY KEY (id, occurred_at)

Куда дальше

  • PG → Партиционирование — нормативные формулировки.
  • Multi-tenancy — row-per-tenant vs партиции.
  • Materialized views — альтернатива для агрегатов.
  • Расширения — pg_partman.
  • VACUUM — autovacuum на партициях.
  • Миграции без даунтайма — миграция в партиции.