open atlas
↑ К треку
SQL и PostgreSQL вглубь SQL · 08 · 06

Рабочий процесс тюнинга

Систематический цикл: найти медленный запрос через pg_stat_statements, воспроизвести через EXPLAIN (ANALYZE, BUFFERS), диагностировать настоящую причину, применить наименьший фикс, проверить, повторить. Измеряй, не гадай.

SQL Senior ◷ 16 min
Уровень
ОсновыJuniorMiddleSenior

Два инженера получают один тикет: «приложение тормозит». Первый открывает кодовую базу и начинает вешать индексы на каждую колонку, которую трогает WHERE — через три деплоя не быстрее, а записи теперь медленнее. Второй запускает один запрос к pg_stat_statements, находит, что 78% общего времени БД — это один отчётный запрос, делает EXPLAIN, видит промах оценки, запускает ANALYZE и закрывает тикет за двадцать минут. Потолок навыка тот же, исход противоположный. Разница — это цикл, а не трюк. Этот урок — цикл, связывающий весь раздел воедино.

Шаг 1 — найди запрос, который реально важен

Нельзя тюнить то, что не измерил, а интуиция про «медленный запрос» почти всегда неверна. Первый ход — pg_stat_statements — расширение, агрегирующее каждый выполненный запрос (нормализованный, со снятыми литералами) и отслеживающее его суммарную стоимость. Важное число — total_exec_time, а не mean_exec_time: запрос на 5 мс, выполненный 2 миллиона раз, стоит больше общего времени БД, чем 4-секундный отчёт, запущенный дважды, и срезание первого экономит больше.

-- Запросы, съедающие больше всего общего времени на сервере:
SELECT
  substring(query, 1, 60)        AS query,
  calls,
  round(total_exec_time)         AS total_ms,
  round(mean_exec_time, 2)       AS mean_ms,
  round(100 * total_exec_time / sum(total_exec_time) OVER (), 1) AS pct
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 10;

Читай колонку pct: тюнинг это Парето. Если один запрос — 78% общего времени, это весь твой день; остальное — шум, пока не станет важным. Сортируй по total_exec_time, бери верхний, игнорируй всё остальное.

Шаги 2–4 — воспроизведи, диагностируй, почини наименьшее

Когда у тебя есть тот самый запрос, цикл механистичен:

  • Воспроизведи через EXPLAIN (ANALYZE, BUFFERS) (урок 01). Это истина в последней инстанции — никогда не тюнь по одному тексту запроса.
  • Диагностируй, сопоставив улики с одной корневой причиной (весь раздел, сжато):
    • Большой зазор оценка-против-факта по строкам? → устаревшая или предполагающая независимость статистика → ANALYZE или CREATE STATISTICS (урок 03).
    • Seq Scan на селективном фильтре, высокие read= буферы? → отсутствующий индекс → добавь (CONCURRENTLY), по главе индексов трека databases.
    • Живых строк мало, но буферы/размер огромны? → bloat → вакуум/pg_repack (урок 04).
    • Оценки в порядке, скан верный, всё равно медленно? → данных правда слишком много → перепиши, пагинируй или предагрегируй.
  • Примени наименьший фикс, адресующий эту причину. Одно изменение. Точечный ANALYZE лучше нового индекса; новый индекс лучше переписывания; переписывание лучше изменения конфига — в этом порядке локальности, ведь наименьшее изменение имеет наименьший радиус поражения.
  • Проверь, перезапустив EXPLAIN (ANALYZE, BUFFERS) и убедившись, что число сдвинулось. Затем пересмотри pg_stat_statements после реального трафика — иногда фикс смещает узкое место на следующий запрос, и цикл повторяется.

Почему «измеряй, не гадай» — это вся дисциплина

Каждый анти-паттерн тюнинга — это догадка, пропустившая шаг измерения:

  • Преждевременная индексация — добавление индексов «на всякий случай» до того, как хоть один запрос доказал, что нужен. Каждый индекс облагает налогом каждую запись (глава индексов трека databases это количественно оценивает) и может вводить планировщик в заблуждение; неиспользуемый индекс — чистая цена. Добавляй индекс только после того, как EXPLAIN покажет Seq Scan, который он уберёт.
  • Карго-культ конфига — копирование значений random_page_cost или work_mem из блога без измерения своей нагрузки.
  • Слепое переписывание — джуниор из хука переписал SQL трижды, потому что текст казался медленным, когда реальной причиной была устаревшая оценка, которую переписывание не могло тронуть.

Сеньорский рефлекс обратен: каждому изменению предшествует измерение, его оправдывающее, и следует измерение, его подтверждающее. Это и заставляет цикл сходиться, а не молотить впустую. (Капстоун раздела 09 прогоняет этот самый цикл от начала до конца на реалистичном аналитическом API — этот урок — метод, который он применяет.)

Почему это работает

Почему сортировать по total_exec_time, а не mean_exec_time? Потому что ты оптимизируешь бюджет времени сервера, а общее время — это то, что пользователи коллективно ждут. Запрос со средним 2 мс, но вызываемый 50 миллионов раз в день, жжёт куда больше совокупного времени — и больше конкуренции за блокировки, больше оборота буферов, больше CPU — чем 30-секундный месячный отчёт. Починка запроса с 2 мс до 1 мс экономит больше реального времени по всем пользователям, чем вдвое срезанный отчёт. mean_exec_time важен для SLA латентности одного пользовательского запроса, но для «где база проводит свою жизнь?» доминирует total. Используй обе линзы: total — найти системную цену, mean — защитить конкретную интерактивную точку.

Расставь шаги по порядку

Расставь систематический цикл тюнинга для тикета «приложение тормозит»:

  1. 1 Запроси pg_stat_statements; сортируй по total_exec_time, чтобы найти самый дорогой запрос
  2. 2 Воспроизведи этот запрос через EXPLAIN (ANALYZE, BUFFERS)
  3. 3 Диагностируй единственную корневую причину: промах оценки, отсутствующий индекс, bloat или слишком много данных
  4. 4 Примени наименьший фикс, адресующий эту причину (например, ANALYZE до добавления индекса)
  5. 5 Проверь, перезапустив EXPLAIN и убедившись, что число реально сдвинулось
  6. 6 Пересмотри pg_stat_statements после трафика; вернись к новому верхнему запросу
Викторина

В pg_stat_statements какая колонка говорит, где база тратит больше всего времени в целом?

Викторина

EXPLAIN ANALYZE показывает большой зазор оценка-против-факта и nested loop. Какой наименьший верный первый фикс?

↻ задержка
Вспомните перед уходом
  1. 01
    Каков первый шаг цикла тюнинга и по какой колонке pg_stat_statements сортировать и почему?
  2. 02
    Имея EXPLAIN ANALYZE, как сопоставить улики с наименьшим фиксом?
  3. 03
    Почему «измеряй, не гадай» — ядро дисциплины и какие анти-паттерны это предотвращает?
Итог

Тюнинг — это цикл, и его прогон отличает сеньорскую работу от барахтанья. Найди запрос, который реально важен, через pg_stat_statements, сортируя по total_exec_time — тюнинг это Парето, и дешёвый запрос, выполненный миллионы раз, перевешивает редкий дорогой. Воспроизведи его через EXPLAIN (ANALYZE, BUFFERS); никогда не тюнь по тексту. Диагностируй единственную корневую причину, читая улики — большой зазор оценка-против-факта значит статистику (урок 03), Seq Scan на селективном фильтре значит отсутствующий индекс, мало живых строк в огромной таблице значит bloat (урок 04), а чистый план, всё равно медленный, значит данных правда слишком много. Примени наименьший фикс для этой причины, в порядке локальности — ANALYZE до индекса до переписывания до конфига — ведь наименьшее изменение имеет наименьший радиус поражения. Проверь, что число сдвинулось, затем вернись в начало, ведь фикс часто продвигает следующий запрос наверх. Связующая нить — измеряй, не гадай: каждому изменению предшествует измерение, его оправдывающее, и следует измерение, его подтверждающее. Теперь, когда приходит тикет «приложение тормозит», ты знаешь: первый запрос, который нужно запустить, — не в кодовой базе, а к pg_stat_statements.

Практика

Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.

вспомнитьприменитьуглубить0 из 8 завершено
Связанные уроки

Что-то непонятно?

Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.

Примени это

Примени этот урок в реальном проекте.

хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.