Вот реальный кейс из практики. Таблица с данными о транзакциях весила 4 ТБ и росла на 50 ГБ в день. Запросы за последний месяц работали быстро благодаря индексам, но исторические отчеты могли считаться часами. Решение — партиционирование по месяцам.
Первый шаг — создание структуры:
CREATE TABLE transactions_new (
id bigint,
created_at timestamp,
amount decimal,
-- другие поля
) PARTITION BY RANGE (created_at);
-- Создаем партиции для каждого месяца
CREATE TABLE transactions_2024_01
PARTITION OF transactions_new
FOR VALUES FROM ('2024-01-01') TO ('2024-02-01');
Дальше начинается самое сложное — перенос данных. Прямой INSERT SELECT на таких объемах заблокирует таблицу на часы. Правильный путь — батчинг с параллельной обработкой:
-- Функция для переноса данных за один день
CREATE OR REPLACE FUNCTION migrate_day(day_start date)
RETURNS integer AS $$
BEGIN
RETURN WITH moved AS (
DELETE FROM transactions
WHERE created_at >= day_start
AND created_at < day_start + interval '1 day'
RETURNING *
)
INSERT INTO transactions_new
SELECT * FROM moved;
END;
$$ LANGUAGE plpgsql;
-- Запускаем параллельно для разных дней
SELECT migrate_day(day)
FROM generate_series('2023-01-01'::date, '2024-01-01', '1 day') day;
Во время миграции критично следить за нагрузкой на диски и CPU. Утилита pg_stat_progress_copy показывает прогресс операций. А автовакуум будет очищать старые версии строк, не давая таблице разрастаться.
Отдельная история — обслуживание партиций. Старые данные можно архивировать, перенося целые партиции на медленные диски:
-- Перемещаем старую партицию на другое табличное пространство
ALTER TABLE transactions_2023_01
SET TABLESPACE cold_storage;
-- Отключаем автовакуум для старых партиций
ALTER TABLE transactions_2023_01
SET (autovacuum_enabled = false);
Для автоматизации рутины помогает расширение pg_partman. Оно создает новые партиции заранее и может автоматически архивировать старые:
SELECT partman.create_parent(
'public.transactions',
'created_at',
'range',
'month',
p_premake := 3
);
И последнее: партиционирование требует изменений в приложении. Запросы без указания партиционного ключа будут сканировать все партиции. А значит, нужно научить ORM всегда добавлять created_at в условия поиска.
🏴☠️ @happy_devops