Когда приложение начинает тормозить, а мониторинг показывает аномальный рост времени ответа базы данных, каждая минута простоя стоит денег и нервов. Задача первых тридцати минут - не выдать идеальное решение, а локализовать проблему, снизить остроту и подготовить почву для плановой оптимизации. Этот материал - практическая последовательность шагов, которую можно выполнять прямо во время инцидента.
Почему первые 30 минут решают всё
В режиме «пожара» легко наломать дров: перезагрузить сервер, обрубить активные сессии или начать менять параметры конфигурации, не разобравшись в причине. Первые полчаса нужны, чтобы трезво оценить масштаб и понять, что именно тормозит - один кривой запрос, массовая блокировка, устаревшая статистика или утечка ресурсов. Действуйте от наблюдения к гипотезе, а не наоборот.
Шаг 1. Найти проблемные запросы
Пока пользователи пишут в чат «всё медленно», вы находите конкретных виновников.
pg_stat_activity - кто висит прямо сейчас
Первое, что стоит проверить, - активные и ожидающие сессии. Запрос к pg_stat_activity покажет запросы, выполняющиеся дольше обычного, их состояние и время старта. Обратите внимание на статусы active, idle in transaction и waiting. Подозрительные сессии - те, что висят десятки секунд или минут там, где ожидается отклик в миллисекундах.
Полезно сразу отфильтровать системные процессы и посмотреть на пользовательские соединения. Если один и тот же запрос повторяется в нескольких строках с разными параметрами - это первый кандидат на углублённый анализ.
pg_stat_statements - самые тяжёлые запросы за всё время
Если расширение pg_stat_statements включено, вы получаете агрегированную картину по всем нормализованным запросам. Для поиска проблем смотрите на столбцы mean_exec_time (среднее время выполнения) и total_exec_time (суммарное время). Запросы с высоким средним временем и большим количеством вызовов почти наверняка участвуют в деградации.
Кроме того, обратите внимание на столбцы shared_blks_hit и shared_blks_read: резкий рост чтений с диска по сравнению с попаданиями в кеш может говорить о нехватке памяти или внезапном вымывании буферного кеша.
Журналы медленных запросов (log_min_duration_statement)
Если логирование медленных запросов настроено (например, порог 500 мс или 1 секунда), просмотрите журнал за последние минуты. Ищите повторяющиеся паттерны или запросы, которые раньше не попадали в slow log, а теперь заполонили его. Это быстрый способ подтвердить, что проблема действительно в конкретном типе операций, а не в общей деградации сервера.
Шаг 2. Прочитать план выполнения
Найдя подозрительный запрос, следующий обязательный шаг - заглянуть в его план выполнения.
EXPLAIN (ANALYZE, BUFFERS) - без него никуда
Выполните EXPLAIN (ANALYZE, BUFFERS) для проблемного запроса (если это безопасно для текущей нагрузки - например, на реплике или с ограничением по времени через statement_timeout). Анализируйте не предполагаемую стоимость, а фактические времена и количество строк по каждому узлу. Именно расхождение между ожидаемым и реальным поведением чаще всего указывает на корень проблемы.
Три главных тревожных сигнала в плане
Оценка числа строк отличается на порядок и более.
Если планировщик ожидал 100 строк, а реально пришло 100 000, он выбрал неподходящий тип соединения или порядок операций. Такое случается при устаревшей статистике или сильно неравномерном распределении данных в столбцах.Появление Seq Scan на больших таблицах там, где должен быть Index Scan.
Последовательное сканирование многомиллионной таблицы при каждом вызове запроса - частая причина внезапного замедления. Причиной может быть отсутствие нужного индекса, блокировка его использования из-за наложения функций на колонку вWHEREили слишком большой ожидаемый процент выбираемых строк.Узел-рекордсмен по времени исполнения.
Найдите в дереве плана узел, который отъедает львиную долю времени. Это и есть ваше бутылочное горлышко. Оно подскажет, куда направить усилия - добавить индекс, переписать условие, разбить запрос или обновить статистику.
Шаг 3. Проверить статистику и обслуживание таблиц
Планировщик PostgreSQL полагается на актуальность статистики и физическое состояние данных.
Когда ANALYZE не делали сто лет
Если оценки селективности в плане сильно ошибаются, скорее всего, статистика по одной или нескольким таблицам устарела. Проверьте, когда в последний раз запускался ANALYZE для этих таблиц (можно косвенно оценить по изменениям количества строк после последнего автоанализа или по системным каталогам). Если таблицы активно изменялись, а ANALYZE не выполнялся, принудительно запустите его - в большинстве случаев это безопасно и быстро.
Для колонок с перекошенным распределением значений (например, статус заказа: 99% завершённых и 1% новых) стоит задуматься об увеличении ALTER TABLE ... SET STATISTICS после завершения инцидента, чтобы планировщик собирал более детальную гистограмму.
«Раздутость» (bloat) - быстрый взгляд и что с ней делать без остановки сервиса
Таблицы и индексы со временем могут «распухать» из-за особенностей MVCC: обновлённые и удалённые строки не освобождают место сразу. Чрезмерная раздутость заставляет сканировать больше страниц, увеличивает I/O и замедляет запросы. В первые 30 минут можно оценить масштаб бедствия с помощью запросов к pg_stat_user_tables (сравнивая n_live_tup и n_dead_tup) или специализированных скриптов.
Если раздутость признана вероятной причиной тормозов, в экстренном порядке можно выполнить VACUUM (не FULL) на проблемной таблице - он не блокирует конкурентный доступ. Полноценную переупаковку через pg_repack или VACUUM FULL оставьте на период плановых работ после инцидента, так как они требуют длительных блокировок или создают копию данных.
Шаг 4. Индексы: есть, нет, не используются
Даже когда индексы физически присутствуют, запрос может игнорировать их по разным причинам.
Как за минуту проверить, что индексы подходят под условия
Выведите структуру таблицы в psql (\d table_name) и сопоставьте столбцы индексов с теми, которые используются в фильтрах WHERE и соединениях JOIN. Если запрос фильтруется по колонке, которой нет ни в одном индексе, и таблица большая - вы нашли точку приложения усилий. Убедитесь также, что тип индекса соответствует типу операций (B-tree для равенств и диапазонов, GIN для полнотекстового поиска и т.д.).
Частые ошибки в запросах, убивающие индексы
- Применение функций к индексированной колонке:
WHERE LOWER(name) = 'john'. Индекс поnameне будет задействован. Решение - функциональный индексCREATE INDEX ON users (LOWER(name))или переписывание условия. - Использование
LIKE '%text%'с ведущим символом подстановки. B-tree индекс тут бесполезен, нужен GIN/триграмный индекс. - Ожидание, что индекс по (a, b) ускорит запрос с условием только по столбцу
b. В PostgreSQL это возможно только при определённых условиях (Index Skip Scan появился с 17 версии), но в общем случае придётся создавать отдельный индекс или пересматривать порядок столбцов.
Добавить индекс срочно, но аккуратно (CREATE INDEX CONCURRENTLY)
Если вы точно определили отсутствующий индекс и его добавление способно кардинально снизить нагрузку, создавайте его с опцией CONCURRENTLY. Это позволит новому индексу строиться без долгой эксклюзивной блокировки таблицы. Учтите, что создание индекса всё равно потребляет ресурсы процессора и диска, поэтому в разгар инцидента оцените, не добьёт ли эта операция сервер окончательно. В крайнем случае можно запустить построение на реплике, а после переключиться на неё.
Шаг 5. Блокировки и конкуренция
Медленные запросы не всегда виноваты сами - иногда они становятся жертвами ожидания освобождения ресурсов.
Кто кого заблокировал и как это увидеть
Используйте pg_stat_activity, но теперь с прицелом на столбцы wait_event_type и wait_event. Если видите много сессий с Lock или LWLock, значит, идёт борьба за блокировки. Связка pg_locks с pg_stat_activity покажет, какие транзакции удерживают блокировки и какие их ждут. Типичный сценарий: длительная транзакция, которая обновляет строку и не завершается, блокирует всех остальных, работающих с той же таблицей.
В первые 30 минут не стоит сразу убивать «мешающую» транзакцию, если не уверены на 100% в её безболезненном откате. Но подсветить проблему для дальнейшего разбора - обязательно.
Высокий CPU и диск - не всегда запрос-убийца
Посмотрите на утилизацию ресурсов сервера в целом. Если процессор загружен на 90% и iowait низкий, вероятно, множество неоптимальных запросов создают избыточную вычислительную нагрузку. Если iowait зашкаливает, а дисковая подсистема медленная, даже простые запросы могут упираться в скорость чтения‑записи. Проверьте, нет ли аномально частой записи в WAL или операций временных файлов (запросы с сортировкой, созданием хешей на диске). Это даст понять, надо ли смотреть в сторону work_mem или физических характеристик дисков.
Шаг 6. Быстрый осмотр конфигурации
Неправильные настройки могут сделать нормальные запросы медленными даже при хорошей статистике и индексах.
Параметры памяти, которые стоит проверить сразу
shared_buffers- размер кеша страниц. Если он выставлен слишком низко (например, стандартные 128 МБ при десятках гигабайт данных), большая часть чтений уходит на диск.work_mem- память для сортировок и хеш-таблиц на одну операцию. Если запросы используют много сортировок илиHash Join, аwork_memмаловато, PostgreSQL сбрасывает данные на диск, и запрос резко замедляется. Увеличивать его прямо в бою стоит очень осторожно: несколько одновременных сессий с высокимwork_memмогут вызвать OOM killer.effective_cache_size- оценка памяти, доступной для кеширования на уровне ОС. Не влияет напрямую на потребление, но задаёт планировщику ориентир при выборе между индексным и последовательным сканированием. Неверное значение может склонять планировщик к Seq Scan даже при наличии индекса.
Чего ни в коем случае не делать в первые 30 минут
- Не менять фундаментальные настройки сервера (перезагрузка,
shared_buffers) без чёткого обоснования и без возможности быстро откатить. - Не выполнять
VACUUM FULLилиREINDEXна горячем проде без крайней нужды. - Не сбрасывать статистику
pg_stat_statementsбез предварительного сохранения - потеряете историческую картину, которая может пригодиться через 20 минут. - Не убивать все активные соединения скопом, особенно если не знаете, какие из них держат важные транзакции.
Что дальше: стабилизация и план действий после первого получаса
Первые 30 минут - это разведка. К этому моменту у вас уже должны быть на руках:
- один или несколько подозрительных запросов;
- их планы выполнения с явными аномалиями;
- понимание, связана ли проблема с отсутствием индекса, устаревшей статистикой, блокировками или нехваткой памяти.
Дальнейшие шаги зависят от первопричины. Если виновата статистика - ANALYZE и временное отключение проблемного запроса на прикладном уровне (если возможно). Если отсутствует индекс - построение с CONCURRENTLY в плановом окне или, при крайней необходимости, немедленно на реплике. Если блокировки - координация с разработчиками для ускорения или отмены зависшей транзакции.
Главное - задокументируйте наблюдения сразу, пока память свежая: время начала, список запросов, скриншоты планов, выводы. Это послужит отправной точкой для полноценного разбора инцидента (post-mortem) и предотвращения повторения в будущем.
FAQ: Медленные запросы в PostgreSQL - первые минуты
Как за 1 минуту найти самый медленный запрос в PostgreSQL?
Запросите pg_stat_statements с сортировкой по mean_exec_time DESC или total_exec_time DESC и ограничением в 5–10 строк. Это покажет запросы с наибольшим средним и суммарным временем выполнения.
Почему EXPLAIN показывает одно, а реальное время другое?
EXPLAIN без ANALYZE оперирует оценками планировщика, которые могут сильно расходиться с реальностью из-за устаревшей статистики или неточного предсказания числа строк. Всегда используйте EXPLAIN ANALYZE, чтобы видеть фактические цифры.
Что делать, если Seq Scan на большой таблице, а индекс есть?
Проверьте, не применяется ли функция к индексированной колонке в WHERE, нет ли оператора LIKE '%...', соответствует ли порядок столбцов в составном индексе условиям запроса. Если всё чисто, но планировщик упорно выбирает последовательное сканирование, оцените настройки random_page_cost и effective_cache_size - возможно, они дезориентируют планировщик.
Как часто нужно делать ANALYZE?
Обычно достаточно настроек автоочистки (autovacuum), но после массовых изменений в таблице (загрузка, массовое обновление, удаление) стоит принудительно выполнить ANALYZE. Точной универсальной периодичности нет - ориентируйтесь на расхождения в планах запросов.
Можно ли создать индекс в продакшене без простоя?
Да, конструкция CREATE INDEX CONCURRENTLY строит индекс без длительной эксклюзивной блокировки таблицы, что позволяет продолжать чтение и запись. Однако операция потребляет ресурсы, поэтому лучше выполнять её в период пониженной нагрузки.
Чем опасно резкое изменение work_mem во время инцидента?
Параметр действует на каждую операцию сортировки или хеширования, и при умножении на количество одновременных запросов суммарное потребление памяти может превысить физический объём RAM, что вызовет вызов OOM killer и падение сервера. Меняйте его только с точным пониманием текущего числа параллельных сессий.
Нужно ли перезагружать PostgreSQL, если запросы начали тормозить?
Нет, перезагрузка в слепую - худшее, что можно сделать. Вы потеряете текущие сессии, кеш буферов и всю картину инцидента. Сначала диагностируйте причину по шагам выше.
Вывод
Первые полчаса при расследовании медленных запросов в PostgreSQL - это не время для героических правок конфигурации, а момент спокойной и методичной диагностики. Последовательно проверьте проблемные запросы, их планы, статистику, индексы, блокировки и базовые параметры памяти. Почти всегда хотя бы одна из этих проверок указывает на первопричину. Действуйте по чек-листу, фиксируйте находки и только потом переходите к исправлению - так вы не превратите локальный сбой в масштабную аварию.
Источники
- PostgreSQL slow queries - 7 ways to find and fix performance bottlenecks
- PostgreSQL: debugging a slow query and optimizing it
- Slow Query Questions - PostgreSQL wiki
- Identify Slow Running Query - Azure Database for PostgreSQL
- PostgreSQL Slow Query Fix: 47s to 3.6ms (DBA Cheat Sheet #5 )
- Fixing slow queries in Postgresql : r/dataengineering



.svg.webp)





