AdTech AI PostgreSQL О компании
PostgreSQL · Оптимизация 14 февраля, 2025 Сергей Томулевич

PostgreSQL. Хочешь похудеть? Спроси меня как

Кейс оптимизации размера PostgreSQL базы 505 GB. VACUUM FULL, неиспользуемые таблицы и индексы, фрагментация, временные файлы. Экономия 137 GB.

Кейс оптимизации размера PostgreSQL базы данных объёмом 505 GB. После проведения VACUUM FULL размер уменьшился на 104 GB — разберём в причинах раздувания базы и предложим систематический подход к её сокращению.

Первичный осмотр

Логи

Регулярная ротация логов PostgreSQL обязательна. Дневные логи высоконагруженных баз могут превышать размер самой базы. Параметры настройки:

log_filename                = 'pg.base.%Y-%m-%d.log'
log_rotation_size           = ...
log_rotation_age            = ...
log_directory               = '/spool/log/postgresql'

Логи должны доставляться на резервный сервер и не накапливаться на продакшене.

WAL файлы

Проблемы с WAL возникают при:

Рекомендуемые параметры:

statement_timeout                   = 100000
lock_timeout                        = 100000
idle_in_transaction_session_timeout = 100000

Требуется проверить содержимое pg_wal (или pg_xlog для версии 9.6 и ниже).

Временные файлы

Временные таблицы в pgbouncer могут не удаляться автоматически. Поиск:

SELECT
    n.nspname as SchemaName,
    c.relname as RelationName,
    pg_size_pretty(pg_relation_size(n.nspname ||'.'|| c.relname)) as RelationSize
FROM pg_catalog.pg_class c
LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
WHERE c.relkind IN ('r','s')
AND (n.nspname !~ '^pg_toast' and nspname like 'pg_temp%')
ORDER BY pg_relation_size(n.nspname ||'.'|| c.relname) DESC;

Лишние объекты базы данных

Неиспользуемые таблицы

Сброс статистики:

select pg_stat_reset();

Поиск неиспользуемых таблиц:

SELECT
    schemaname,
    relname,
    pg_size_pretty(pg_relation_size(schemaname ||'.'|| relname)) as RelationSize
FROM
    pg_stat_all_tables
WHERE
    schemaname NOT IN ('pg_catalog', 'pg_toast', 'information_schema') AND
    seq_scan + idx_scan = 0
ORDER BY
    pg_relation_size(schemaname ||'.'|| relname) DESC;

В описанном кейсе таблицы с префиксом tmp_* занимали 18 GB.

Неиспользуемые индексы

SELECT
    schemaname,
    relname,
    indexrelname,
    pg_size_pretty(pg_relation_size(schemaname ||'.'|| indexrelname)) as RelationSize
FROM
    pg_stat_all_indexes
WHERE
    schemaname NOT IN ('pg_catalog', 'pg_toast', 'information_schema') AND
    idx_scan = 0 AND
    indexrelname NOT LIKE '%pkey%'
ORDER BY
    pg_relation_size(schemaname ||'.'|| indexrelname) DESC;

Неиспользуемые индексы занимали 14 GB.

Итого очищено мусора: 33 GB.

Фрагментация

Теория

Фрагментация индексов

Для PostgreSQL 12+ возможна конкурентная переиндексация. Для более старых версий рекомендуется создать новый индекс конкурентно и удалить старый.

Фрагментация таблиц

Обычный VACUUM не решает проблему полупустых страниц. Варианты решения:

1. С блокировкой (быстро):

CREATE TABLE "public"."test_new" AS TABLE "public"."test";

BEGIN;
ALTER TABLE "public"."test" RENAME TO "public"."test"_old;
ALTER TABLE "public"."test"_new RENAME TO "public"."test";
COMMIT;

2. Без блокировки (долго): через триггер синхронизации и хранимую процедуру порционного копирования.

Резюме

После всех операций:

Значительное уменьшение (в несколько раз) указывает на проблемы с конфигурацией или обслуживанием базы данных. Мусор на 33 GB свидетельствует о недостаточном контроле.

Готовы посчитать собственный стек? Расскажем, какие модули и какая нагрузка нужны под вашу задачу.

to@prototypes.ventures