Appearance
Декларативное секционирование в PostgreSQL
PostgreSQL позволяет декларировать, что некоторая таблица разделяется на секции. Разделённая на секции таблица называется секционированной таблицей. Декларация секционирования состоит из одного из трех методов секционирования и списка столбцов или выражений, образующих ключ секционирования.
Сама секционированная таблица является «виртуальной» и как таковая не хранится. Хранилище используется её секциями, которые являются обычными таблицами, связанными с секционированной. В каждой секции хранится подмножество данных таблицы, определяемое её границами секции. Все строки, вставляемые в секционированную таблицу, перенаправляются в соответствующие секции в зависимости от значений столбцов ключа секционирования. Если при изменении значений ключа секционирования в строке она перестаёт удовлетворять ограничениям исходной секции, эта строка перемещается в другую секцию.
Что такое распределенные данные?
Распределенные данные — это подход к организации хранения информации, при котором данные логически связаны, но физически разделены на части (секции или партиции). В контексте баз данных это означает разделение одной большой таблицы на несколько меньших таблиц, которые хранятся отдельно, но воспринимаются системой как единое целое.
Когда таблица становится очень большой (миллионы или миллиарды строк), операции с ней замедляются: увеличивается время выполнения запросов, резервного копирования, обслуживания индексов. Секционирование решает эти проблемы, позволяя:
- Ускорить выполнение запросов за счет ограничения области поиска (partition pruning)
- Упростить управление данными (можно удалять или архивировать целые секции)
- Улучшить параллельную обработку запросов
- Оптимизировать использование хранилища (разные секции можно размещать на разных дисках)
Что такое декларативное секционирование?
Декларативное секционирование — это подход, появившийся в PostgreSQL начиная с версии 10, который позволяет определять правила разделения данных на уровне DDL (Data Definition Language). В отличие от наследованного секционирования, которое требовало ручного управления триггерами и проверочными ограничениями, декларативное секционирование встроено в ядро СУБД и управляется автоматически.
Основные преимущества:
- Автоматическая маршрутизация данных в нужные секции
- Автоматическое исключение нерелевантных секций при выполнении запросов (partition pruning)
- Упрощенный синтаксис создания и управления секциями
- Поддержка первичных и внешних ключей на секционированных таблицах
- Возможность создания индексов как на всей таблице, так и на отдельных секциях
Ограничение:
- Преобразовать обычную таблицу в секционированную и наоборот нельзя.
Однако в секционированную таблицу можно добавить в качестве секции существующую обычную или секционированную таблицу, а также можно удалить секцию из секционированной таблицы и превратить её в отдельную таблицу; это может ускорить многие процессы обслуживания.
Критерии разделения и их выбор
Выбор правильного ключа секционирования — критически важное решение, которое влияет на производительность и удобство управления данными.
Критерии разделения:
- Временной (дата, месяц, год) — наиболее популярный подход для данных с временной меткой
- Географический (регион, страна, город) — для данных, привязанных к местоположению
- Бизнес-логика (тип клиента, категория продукта) — для доменно-специфичных данных
- Равномерное распределение (хэш) — когда нужна равномерная нагрузка на секции
Как выбрать критерий:
- Анализируйте типичные запросы — секционирование должно ускорять самые частые операции
- Оценивайте объем данных в каждой секции — секции должны быть сопоставимы по размеру
- Продумайте стратегию удаления старых данных — временное секционирование упрощает архивацию
- Учитывайте рост данных — критерий должен оставаться эффективным при увеличении объема
Пример выбора: Для таблицы с продажами логично использовать секционирование по месяцу, так как типичные запросы фильтруют данные по периоду, а старые данные можно легко архивировать удалением целых секций.
Секционирование по диапазону (RANGE)
Секционирование по диапазону разделяет данные на основе непрерывных интервалов значений. Чаще всего используется для временных данных.
sql
-- создание секционированной таблицы по дате
create table sales (
sale_id serial,
sale_date date not null,
product_id int,
amount decimal(10, 2),
region varchar(50),
primary key (sale_id, sale_date)
) partition by range (sale_date);
-- создание секций для каждого квартала 2026 года
create table sales_q1_2026 partition of sales
for values from ('2026-01-01') to ('2026-04-01');
create table sales_q2_2026 partition of sales
for values from ('2026-04-01') to ('2026-07-01');
create table sales_q3_2026 partition of sales
for values from ('2026-07-01') to ('2026-10-01');
create table sales_q4_2026 partition of sales
for values from ('2026-10-01') to ('2027-01-01');
-- создание секции по умолчанию для данных, не попадающих в определенные диапазоны
create table sales_default partition of sales default;При выполнении запроса с фильтром по дате PostgreSQL автоматически исключит (prune) нерелевантные секции, что значительно ускорит выполнение.
Важно:
- Нижняя граница включается в диапазон, верхняя нет.
- Специальные значения MINVALUE и MAXVALUE могут использоваться при создании диапазонной секции для указания, что нижняя или верхняя граница для значений столбца отсутствует.
Секционирование по списку (LIST)
Секционирование по списку разделяет данные на основе дискретных значений столбца. Идеально подходит для категориальных данных.
sql
-- создание секционированной таблицы по регионам
create table customers (
customer_id serial,
name varchar(100),
email varchar(100),
region_code char(2) not null,
signup_date date,
primary key (customer_id, region_code)
) partition by list (region_code);
-- создание секций для каждого региона
create table customers_europe partition of customers
for values in ('DE', 'FR', 'IT', 'ES', 'UK');
create table customers_asia partition of customers
for values in ('JP', 'CN', 'KR', 'IN');
create table customers_america partition of customers
for values in ('US', 'CA', 'MX', 'BR');
-- можно создавать секции и для одиночных значений
create table customers_australia partition of customers
for values in ('AU');При запросе данных по конкретному региону будет сканироваться только соответствующая секция.
Секционирование по хэшу (HASH)
Секционирование по хэшу равномерно распределяет данные по секциям на основе хэш-функции от значения столбца. Полезно, когда нет естественного ключа секционирования.
sql
-- создание секционированной таблицы по хэшу от id пользователя
create table user_sessions (
session_id uuid default gen_random_uuid(),
user_id int not null,
login_time timestamp,
ip_address inet,
user_agent text,
primary key (session_id, user_id)
) partition by hash (user_id);
-- создание 4 секций для равномерного распределения
create table user_sessions_p0 partition of user_sessions
for values with (modulus 4, remainder 0);
create table user_sessions_p1 partition of user_sessions
for values with (modulus 4, remainder 1);
create table user_sessions_p2 partition of user_sessions
for values with (modulus 4, remainder 2);
create table user_sessions_p3 partition of user_sessions
for values with (modulus 4, remainder 3);
-- Проверим разделение, для этого внесем через родителя 40000 строк
do $$
begin
for i in 1..40000
loop
insert into user_sessions (user_id)
values(i);
end loop;
end;
$$ language plpgsql;
-- Проверим количество записей в каждой секции:
select
(select count(*) u_s_p0 from user_sessions_p0),
(select count(*) u_s_p1 from user_sessions_p1),
(select count(*) u_s_p2 from user_sessions_p2),
(select count(*) u_s_p3 from user_sessions_p3);text
u_s_p0|u_s_p1|u_s_p2|u_s_p3|
------+------+------+------+
10034| 10067| 10031| 9868|Хэш-секционирование обеспечивает равномерное распределение данных, что помогает сбалансировать нагрузку на ввод/вывод.
Управление секциями: подключение и отключение
PostgreSQL предоставляет гибкие механизмы для динамического управления секциями без простоя системы.
sql
-- 1. отключение секции от таблицы (данные сохраняются в отдельной таблице)
alter table sales detach partition sales_q1_2026;
-- теперь sales_q1_2026 стала обычной таблицей
-- её можно архивировать, удалить или проанализировать отдельно
-- 2. подключение существующей таблицы как секции
-- сначала создадим обычную таблицу с той же структурой
create table sales_q1_2027 (
like sales including all
);
-- заполним её данными
insert into sales_q1_2027 values (...);
-- теперь подключим как секцию
alter table sales attach partition sales_q1_2027
for values from ('2027-01-01') to ('2027-04-01');
-- 3. удаление секции (с удалением данных)
drop table sales_q1_2026; -- если секция уже отключена
-- или через удаление партиции с данными
alter table sales drop partition sales_q1_2026;Отключение секций особенно полезно для архивации старых данных или их миграции на более медленные носители.
Индексы в секционированных таблицах
Работа с индексами в секционированных таблицах имеет свои особенности. Можно создавать как глобальные индексы (на всю таблицу), так и локальные (на отдельные секции).
sql
-- 1. автоматическое создание индексов на секционированной таблице
-- индекс, созданный на родительской таблице, автоматически применяется ко всем секциям
create index idx_sales_date on sales(sale_date);
create index idx_customers_region on customers(region_code);
-- 2. проверка индексов по секциям
select
relname as partition_name,
relkind,
pg_size_pretty(pg_total_relation_size(oid)) as size
from pg_class
where relname like 'sales_q%'
order by relname;
-- 3. создание индекса на конкретной секции (если нужно особое индексирование)
create index idx_sales_q1_amount on sales_q1_2026(amount)
where amount > 1000; -- частичный индекс только для крупных продаж
-- 4. параллельное построение индексов на секциях
-- можно строить индексы на каждой секции параллельно, что ускоряет обслуживание
-- это особенно полезно при создании новой секционированной таблицы на больших объемах данных
-- 5. управление индексами
-- reindex работает и на секционированных таблицах
reindex table concurrently sales;Важно:
- Первичные и уникальные ключи в секционированных таблицах должны включать столбец секционирования. Это ограничение гарантирует уникальность значений внутри всей таблицы, а не только внутри секции.
Заключение
Декларативное секционирование в PostgreSQL — мощный инструмент для управления большими объемами данных, который значительно упрощает архитектуру распределенных данных. Автоматическая маршрутизация данных, исключение нерелевантных секций при запросах и встроенная поддержка индексов делают его отличным выбором для высоконагруженных систем.
Ключевые моменты для успешного внедрения:
- Тщательно выбирайте ключ секционирования на основе паттернов доступа к данным
- Планируйте стратегию управления жизненным циклом данных (архивация, удаление)
- Используйте соответствующий тип секционирования: RANGE для временных данных, LIST для категориальных, HASH для равномерного распределения
- Помните об ограничениях (уникальные ключи должны включать столбец секционирования)
- Мониторьте распределение данных по секциям для предотвращения дисбаланса
При правильном применении декларативное секционирование может значительно ускорить запросы, если паттерны фильтрации совпадают с ключом секционирования, упростить операции обслуживания и масштабировать приложение для работы с постоянно растущими объемами данных.