Блог 08.05.2026 10:00 Язык текста: RU 1 мин чтения

PostgreSQL стал bottleneck: почему индексы не помогают

База упирается в CPU, latency растёт, тяжёлые запросы тормозят, а индексы не дают ожидаемого эффекта. Разбор: execution plan, EXPLAIN ANALYZE, реальный bottleneck и почему добавлять индексы вслепую — плохая стратегия.

PostgreSQL стал bottleneck

Классический сценарий деградации выглядит так: под нагрузкой растёт CPU, latency у ключевых сценариев уезжает вверх, очередь начинает отставать, а приложение всё чаще ждёт базу. Первая реакция почти всегда одинаковая — срочно добавить индексы. Но именно здесь чаще всего и начинается лишняя работа без результата.

Почему индексы “не помогают”

Индекс сам по себе не лечит плохой execution plan. Если запрос устроен неудачно, условие слабо селективно, join раздувает промежуточные наборы данных, а планировщик выбирает дорогой путь, новый индекс может вообще не использоваться или давать минимальный эффект.

Более того, иногда индексы ухудшают ситуацию: увеличивают стоимость записи, раздувают таблицы и создают ложное ощущение, что база уже “оптимизирована”, хотя корень проблемы остаётся в форме запроса или схеме доступа к данным.

Типовые ошибки

  • добавлять индексы без EXPLAIN ANALYZE
  • смотреть на запрос из кода, но не на реальный план выполнения
  • игнорировать фактическое число строк, которые проходит план
  • не проверять сортировки, hash aggregate, nested loop и seq scan в контексте объёма данных
  • оптимизировать один запрос, хотя bottleneck создаёт целый набор похожих запросов

Что нужно делать вместо этого

  • сначала собрать список реально дорогих запросов
  • смотреть EXPLAIN ANALYZE, а не предполагать причину на глаз
  • сравнивать estimated rows и actual rows
  • проверять, какой участок плана даёт основную стоимость
  • решать, нужен ли новый индекс, переписывание условия, изменение join-порядка или batch-подход

Что часто оказывается реальной причиной

На практике bottleneck нередко создают не “плохие индексы”, а комбинации: большой OFFSET, неудачная пагинация, фильтрация по вычисляемым значениям, тяжёлые join по широким таблицам, повтор одного и того же запроса много раз за один request, агрегации по горячим данным без предварительной подготовки.

Практический вывод

Когда PostgreSQL становится bottleneck, добавление индексов без анализа — это почти всегда стрельба вслепую. Сначала нужен реальный execution plan, потом локализация узкого участка, и только после этого — точечное изменение в запросе, индексе или способе чтения данных. Именно такой порядок даёт предсказуемый результат, а не имитацию оптимизации.

Когда нужен paid diagnostic

Если у команды уже есть список тяжёлых запросов и проблема воспроизводится в конкретных сценариях, обычно можно заходить сразу через Performance Triage. Если же “БД грузит CPU”, но никто не понимает, какой именно запрос или путь чтения данных реально создаёт bottleneck, безопаснее и быстрее начать с paid diagnostic, чтобы не стрелять индексами вслепую.

Если у вас похожая ситуация — это уже не “почитать на потом”

Обычно ко мне приходят в одном из трёх случаев:

  • — уже пробовали чинить, но реальная причина всё ещё не локализована
  • — есть риск сделать production хуже неудачной правкой
  • — понятно, что нужен либо короткий диагностический вход, либо прямой вход в пакет

В таких ситуациях дальнейшие попытки двигаться без локализации причины обычно не экономят время, а только увеличивают риск и стоимость следующей ошибки.

Если причина пока неочевидна — вход через Paid Diagnostic / Audit: короткий разбор, локализация проблемы, карта рисков и понятный следующий шаг без длинного бесплатного пресейла.

Если симптом уже понятен и сценарий читается по фактам — обычно можно заходить сразу через Performance Triage, Release Rescue или Emergency Debug & Stabilization.

Быстрое правило выбора:

  • — если неясно, где именно причина → Paid Diagnostic / Audit
  • — если причина уже понятна и нужен результат → прямой вход в пакет
Я