Траблшутинг раздувания индексов (Index Bloat) в PostgreSQL: REINDEX CONCURRENTLY
- Резкое замедление выполнения SELECT-запросов, использующих индексное сканирование (Index Scan / Index Only Scan).
- Объем индексов на диске значительно превышает размер самих таблиц при отсутствии роста количества записей.
- Увеличение времени прогрева буферного кэша (Buffer Cache Hit Ratio падает, растет Read I/O).
- Высокая фрагментация B-Tree страниц после массовых операций UPDATE и DELETE.
1. Диагностика процента раздувания (Bloat) через расширение pgstattuple
CREATE EXTENSION IF NOT EXISTS pgstattuple;
-- Проверка конкретного индекса
SELECT * FROM pgstatindex('idx_orders_created_at');
-- Вычисление мертвого пространства (dead_tuple_percent / free_space)2. Запрос для поиска топ-10 самых раздутых индексов в БД
SELECT
schemaname, tablename, indexname,
pg_size_pretty(pg_relation_size(indexrelid)) AS index_size,
pg_size_pretty(pg_relation_size(indrelid)) AS table_size
FROM pg_stat_user_indexes
ORDER BY pg_relation_size(indexrelid) DESC
LIMIT 10;3. Онлайн-перестроение индекса без эксклюзивных блокировок DML
-- Перестроение конкретного индекса в неблокирующем режиме
REINDEX INDEX CONCURRENTLY idx_orders_created_at;
-- Перестроение всех индексов таблицы
REINDEX TABLE CONCURRENTLY public.orders;4. Тюнинг параметров Autovacuum для предотвращения повторного Bloat
Добавьте настройки в postgresql.conf или примените точечно к таблице:
ALTER TABLE orders SET (
autovacuum_vacuum_scale_factor = 0.05,
autovacuum_vacuum_threshold = 500,
autovacuum_vacuum_cost_limit = 1000
); Частые вопросы (FAQ)
Чем REINDEX CONCURRENTLY отличается от обычного REINDEX?
Обычный REINDEX берет жесткую блокировку ACCESS EXCLUSIVE на таблицу, блокируя любые операции чтения и записи. REINDEX CONCURRENTLY создает дубликат индекса в фоновом режиме под слабой блокировкой ShareUpdateExclusiveLock, не прерывая транзакции приложения.
Что делать, если выполнение REINDEX CONCURRENTLY завершилось ошибкой?
В каталоге останется невалидный индекс с суффиксом '_ccnew' или '_ccold'. Найдите его запросом `SELECT relname FROM pg_class WHERE relisvalid = false;` и удалите командой DROP INDEX CONCURRENTLY <имя_индекса>.