Что произойдёт, если индексов много? Какова цена индексов при INSERT/UPDATE?
Каждый индекс требует обновления при INSERT и при UPDATE индексируемого столбца, блокирует HOT-обновления, увеличивает WAL и замедляет репликацию. Избыточные индексы выявляют через pg_stat_user_indexes (idx_scan = 0).
Что происходит при INSERT с несколькими индексами
При каждом INSERT PostgreSQL обязан обновить все индексы таблицы. Для таблицы с 8 индексами это 8 отдельных B-tree операций записи плюс соответствующие WAL записи для каждой. Стоимость растёт примерно линейно с числом индексов.
HOT update и его условия
При UPDATE PostgreSQL пытается использовать Heap Only Tuple (HOT) обновление — новая версия строки пишется в тот же heap page, а индексы не обновляются. HOT возможен только если: изменённые столбцы не входят ни в один индекс, и в heap page есть свободное место.
Если обновляется индексируемый столбец — HOT невозможен. PostgreSQL создаёт новую версию строки и обновляет все затронутые индексы. Старые index tuples помечаются как мёртвые и убираются при следующем VACUUM.
-- Проверить, сколько индексов у таблицы и как они используются
SELECT
indexrelname,
idx_scan,
idx_tup_read,
idx_tup_fetch,
pg_size_pretty(pg_relation_size(indexrelid)) AS index_size
FROM pg_stat_user_indexes
WHERE relname = 'orders'
ORDER BY idx_scan ASC;
-- Найти неиспользуемые индексы (кандидаты на удаление)
SELECT schemaname, relname, indexrelname, idx_scan
FROM pg_stat_user_indexes
WHERE idx_scan = 0
AND schemaname = 'public'
ORDER BY pg_relation_size(indexrelid) DESC;
Write amplification и WAL
Каждая запись в индекс генерирует WAL. На таблице с 10 индексами один INSERT может генерировать в 5–10 раз больше WAL, чем на таблице без индексов. Это увеличивает:
- Время выполнения INSERT/UPDATE на primary
- Лаг логической и физической репликации
- Нагрузку на диск (особенно на burst write)
- Время bulk load (COPY, pg_restore)
-- При bulk load: удалить индексы до загрузки, восстановить после
-- Это ускоряет загрузку в 3-10 раз для больших таблиц
DROP INDEX CONCURRENTLY idx_orders_status;
COPY orders FROM '/tmp/orders_dump.csv' CSV;
CREATE INDEX CONCURRENTLY idx_orders_status ON orders (status);
ANALYZE orders;
Bloat от UPDATE
После UPDATE индексируемого столбца старый index tuple остаётся в B-tree до VACUUM. Если autovacuum не успевает — индекс раздувается. Bloated индекс занимает больше места и медленнее сканируется. Проверить можно через расширение pgstattuple:
-- Требует расширения pgstattuple
SELECT * FROM pgstatindex('idx_orders_status');
-- Смотреть: dead_tuple_percent > 10% — сигнал к REINDEX CONCURRENTLY
Подводные камни
- Удалять индекс только по нулевому
idx_scanопасно: он может поддерживать PRIMARY KEY, UNIQUE constraint или использоваться редко при аварийных запросах. - Добавление индекса на high-write таблицу без нагрузочного теста может ухудшить p95/p99 latency INSERT в 2–3 раза.
- HOT update блокируется даже одним индексом по обновляемому столбцу — частые UPDATE полей вроде
updated_atилиstatusнакапливают dead tuples быстрее при наличии индексов. - Лаг репликации растёт пропорционально объёму WAL — на write-heavy нагрузке каждый лишний индекс увеличивает лаг на репликах.
- Partial index (
WHERE status = 'active') может быть в 10–100 раз меньше полного и почти не влиять на write overhead для строк, не попадающих в предикат. - Статистика
pg_stat_user_indexesсбрасывается приpg_stat_reset()и после рестарта — низкийidx_scanсразу после деплоя не показателен. - REINDEX CONCURRENTLY на большой таблице создаёт временный индекс рядом — нужно убедиться, что на диске достаточно места (2× размер индекса).
- Индексы на foreign key колонках нужны не для SELECT, а для ускорения DELETE/UPDATE на родительской таблице — удаление такого «неиспользуемого» индекса вызовет full scan дочерней таблицы при каждом DELETE из родительской.
Common mistakes
- Считать индекс бесплатным.
- Удалять индексы без проверки constraints.
- Не связывать индексы с WAL и replica lag.
What the interviewer is testing
- Просит объяснить HOT update.
- Проверяет метрики pg_stat_user_indexes.
- Уточняет write amplification.