linuxlab.io
Учебники▾
  • Линукс и сети
    Файловая система, процессы, TCP/IP, BGP и OSPF
    →
  • Terraform и IaC
    HCL, state, plan/apply на sandbox LocalStack
    →
  • Git и GitHub
    Объектная модель, plumbing, ветвление, GitHub Actions
    →
Все учебники →
ЦеныО платформеВойтиСоздать аккаунт
/
Intro
Lessons
Footer
linuxlab-УчебникиЦеныО платформеКонфиденциальность и куки
Copyright © 2026 LinuxLab. Все права защищены.
linuxlab.io
Учебники▾
  • Линукс и сети
    Файловая система, процессы, TCP/IP, BGP и OSPF
    →
  • Terraform и IaC
    HCL, state, plan/apply на sandbox LocalStack
    →
  • Git и GitHub
    Объектная модель, plumbing, ветвление, GitHub Actions
    →
Все учебники →
ЦеныО платформеВойтиСоздать аккаунт
/
  • Введение
  • Главы
  • How it worksскоро
  • Уроки
  • База знаний
  • Собеседование
Часть X — Капстоун

$ глава 45 · 70 минут

Единый алгоритм оптимизации (капстоун)

Мы прошли все подсистемы по отдельности: хранение, MVCC, vacuum, журнал, блокировки, планировщик, индексы, репликацию, эксплуатацию. В проде они ломаются вместе, и одна и та же жалоба «база тормозит» может идти от любой из них. Эта глава собирает разрозненные знания в один алгоритм принятия решений - и проверяет его на капстоуне.

Капстоун - это расследование деградации прода по снапшоту демо-базы с подсаженными проблемами. Никаких новых механизмов; только умение пройти от симптома к причине, опираясь на всё, что мы вскрывали по странице за раз.

45.1 От симптома к механизму

Оптимизация начинается не с запроса, а с вопроса «что именно медленно». Первое ветвление - масштаб проблемы:

  • Тормозит вся база - смотри систему: горизонт транзакции, bloat, блокировки, нехватка памяти, autovacuum. Это часть про эксплуатацию и наблюдаемость.
  • Тормозит конкретный запрос - смотри его план: статистика, выбор метода доступа, соединения, индексы. Это часть про планировщик.

Спутать эти ветки - значит чинить не то. Медленный запрос на здоровой базе лечат планом; быстрый запрос на больной базе бесполезно оптимизировать, пока горизонт держит мусор. Алгоритм ниже разводит эти ветки явно.

45.2 Ветка «вся база»: горизонт, мусор, блокировки

Когда деградация общая, идёшь по системным представлениям в определённом порядке:

  1. Кто держит горизонт. pg_stat_activity на долгие транзакции и idle in transaction с большим xact_age. Долгий горизонт не даёт vacuum чистить мусор - и дальше всё медленнее.
  2. Сколько мусора. pg_stat_user_tables на n_dead_tup и last_autovacuum. Раздутая таблица читается дольше, потому что живые строки разбросаны среди мёртвых.
  3. Не растёт ли возраст заморозки. age(relfrozenxid) близко к порогу - впереди агрессивный autovacuum или хуже.
  4. Нет ли блокировок. Цепочка pg_locks плюс pg_stat_activity: кто кого ждёт, кто blocker.

Каждый шаг опирается на механизм из своей части. Лечение - закрыть долгую транзакцию, дать vacuum доработать, снять blocker.

45.3 Ветка «один запрос»: статистика, план, индексы

Когда тормозит конкретный запрос, рабочий инструмент - EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON). Алгоритм чтения:

  1. Сравни estimated и actual rows. Сильное расхождение - планировщик ошибся в кардинальности, обычно из-за устаревшей или недостаточной статистики. Лечение - ANALYZE, расширенная статистика.
  2. Найди, где уходит время. Самый дорогой узел, неожиданный Seq Scan на большой таблице, Sort с проливом на диск.
  3. Проверь sargability. Предикат вида WHERE col::date = ... или функция над колонкой убивают индекс - условие надо переписать так, чтобы индекс брался.
  4. Проверь, тот ли метод доступа и тип индекса. btree на полнотекстовом LIKE '%x%' бесполезен - нужен GIN; отсутствие составного индекса под ORDER BY ... LIMIT даёт Sort с большим cost.

Ошибка кардинальности распространяется снизу вверх по дереву плана: неверная оценка на нижнем узле тянет неверный выбор соединения на верхнем. Поэтому читают план с листьев.

45.4 Капстоун: пять подсаженных проблем

В капстоуне демо-база специально испорчена пятью независимыми проблемами, по одной на каждую крупную тему курса. Твоя задача - найти и починить каждую, обосновав диагноз данными:

  1. Долгая idle-in-transaction держит горизонт - растут bloat и age(relfrozenxid), vacuum не убирает мёртвые версии.
  2. «Плохой» запрос с кастом ::date и устаревшей статистикой - Seq Scan вместо Index Scan.
  3. Отсутствует составной индекс под ORDER BY ... LIMIT - Bitmap плюс Sort с гигантским cost.
  4. Дублирующий неиспользуемый индекс - раздувает запись, не давая выигрыша на чтении.
  5. Неверный тип индекса - btree вместо GIN на полнотекстовом поиске.

Каждая чинится механизмом из своей части: закрыть транзакцию и дать vacuum (часть про очистку), ANALYZE и переписать предикат (часть про планировщик), создать правильный индекс и удалить лишний (часть про индексы).

45.4.1 Копнуть глубже: как капстоун проверяется

Грейдер капстоуна - это набор SQL-ассертов поверх системных представлений и JSON-планов, по одному на каждую проблему:

  • горизонт схлопнулся и vacuum убрал мусор: n_dead_tup = 0 на целевой таблице после закрытия транзакции и vacuum;
  • целевой запрос показывает ожидаемый node type (Index Scan, не Seq Scan) и cost ниже порога - проверяется парсингом EXPLAIN (FORMAT JSON);
  • pg_stat_user_indexes подтверждает, что новый индекс используется, а у удалённого дубля сканов больше нет;
  • полнотекстовый запрос на GIN укладывается в заданное время и берёт Bitmap Index Scan, а не Seq Scan.

Это та же методология, которой ты пользовался всю книгу: не «поверь, что починил», а «покажи на представлении и на плане».

45.5 Алгоритм одной картинкой

Сведём всё в блок-схему принятия решений. Это и есть итог книги - карта, по которой идут от жалобы к причине.

Каждый узел этой схемы - отдельная глава курса. Капстоун проводит тебя по обеим веткам сразу. Полный сценарий и автопроверка - в лабе.

Уроки в sandbox

lab-45.1. Капстоун: расследование деградации прода

Перед тобой демо-база с пятью подсаженными проблемами. Пройди единый алгоритм: продиагностируй через системные представления и EXPLAIN, почини каждую проблему, обоснуй диагноз данными. Для каждой сначала предскажи, что увидишь, потом проверь.

  1. Ветка «вся база»: найди, кто держит горизонт - SELECT pid, state, now() - xact_start AS age FROM pg_stat_activity WHERE state = 'idle in transaction' ORDER BY age DESC;. Затем посмотри мусор: SELECT relname, n_dead_tup FROM pg_stat_user_tables ORDER BY n_dead_tup DESC;.

  2. Закрой удерживающую транзакцию (в её сессии COMMIT, либо SELECT pg_terminate_backend(pid)), выполни VACUUM целевой таблицы и подтверди: n_dead_tup упал до 0.

  3. Ветка «один запрос»: возьми запрос с кастом WHERE booked_at::date = ..., посмотри план - Seq Scan. Выполни ANALYZE bookings; и перепиши предикат на sargable диапазон booked_at >= ... AND booked_at < ...; план переходит в Index Scan.

  4. Отсутствующий составной индекс: найди запрос с ORDER BY ... LIMIT, дающий Sort с большим cost; создай составной индекс под него и подтверди Index Scan с малым cost через EXPLAIN (FORMAT JSON).

  5. Лишний индекс: найди дублирующий индекс с нулём сканов в pg_stat_user_indexes (idx_scan = 0) и удали его.

  6. Неверный тип индекса: на полнотекстовом поиске btree даёт Seq Scan; создай GIN-индекс и подтверди Bitmap Index Scan и падение времени.

sandbox с автопроверкой - открыть в песочнице

Резюме

  • Оптимизация начинается с вопроса «что именно медленно»: первое ветвление - вся база (система) или один запрос (план).
  • Ветка «вся база»: по порядку проверяешь горизонт (долгие транзакции, idle in transaction), мусор (n_dead_tup), возраст заморозки и блокировки.
  • Ветка «один запрос»: EXPLAIN ANALYZE, сравнение estimated и actual rows, поиск дорогого узла, проверка sargability и правильности индекса/метода доступа.
  • Ошибка кардинальности распространяется снизу вверх по дереву плана, поэтому план читают с листьев.
  • Капстоун - пять независимых подсаженных проблем (горизонт, устаревшая статистика, отсутствующий и лишний индексы, неверный тип индекса), по одной на крупную тему.
  • Грейдер проверяет каждую починку через SQL-ассерты поверх системных представлений и JSON-планов - та же методология «покажи на представлении», что всю книгу.

Контрольные вопросы

  1. Почему первый шаг оптимизации - различить «вся база» и «один запрос»?

    Показать ответ

    Потому что эти две ситуации лечат разными средствами, и спутать их - значит чинить не то. Если деградация общая (горизонт держит мусор, блокировки, нехватка памяти), оптимизация отдельного запроса бесполезна - он будет медленным на больной базе всё равно. Если же база здорова, а тормозит конкретный запрос, систему трогать незачем - проблема в его плане. Поэтому алгоритм сразу разводит две ветки: системную диагностику и разбор плана.

  2. В каком порядке проверять систему, когда тормозит вся база?

    Показать ответ

    Сначала кто держит горизонт - pg_stat_activity на долгие транзакции и idle in transaction с большим xact_age, потому что долгий горизонт запускает цепочку проблем. Затем сколько мусора - n_dead_tup и last_autovacuum в pg_stat_user_tables. Затем возраст заморозки age(relfrozenxid) - не подходит ли к порогу. И блокировки - pg_locks плюс pg_stat_activity, кто кого ждёт. Каждый шаг опирается на механизм из своей части курса.

  3. Почему план EXPLAIN читают с листьев (снизу вверх)?

    Показать ответ

    Потому что ошибка кардинальности распространяется снизу вверх: неверная оценка строк на нижнем узле заставляет планировщик неверно выбрать соединение и порядок на верхних узлах. Если читать сверху, ты видишь следствие (плохой join), а не причину (плохую оценку на скане). Начав с листьев, находишь первый узел, где estimated сильно разошёлся с actual, - это и есть корень, а всё выше лишь его последствия.

  4. Какие пять проблем подсажены в капстоуне и к каким частям курса они относятся?

    Показать ответ

    (1) Долгая idle-in-transaction держит горизонт - часть про vacuum/очистку; (2) запрос с кастом ::date и устаревшей статистикой даёт Seq Scan - часть про планировщик и статистику; (3) отсутствует составной индекс под ORDER BY ... LIMIT, отсюда Sort с большим cost - часть про проектирование индексов; (4) дублирующий неиспользуемый индекс раздувает запись - тоже индексы; (5) btree вместо GIN на полнотекстовом поиске - выбор метода доступа. По одной проблеме на крупную тему.

  5. Как капстоун проверяет, что проблема действительно починена, а не «вроде стало быстрее»?

    Показать ответ

    Набором SQL-ассертов поверх системных представлений и JSON-планов. Горизонт - n_dead_tup = 0 на целевой таблице после vacuum. Запрос - ожидаемый node type (Index Scan) и cost ниже порога из EXPLAIN (FORMAT JSON). Индексы - pg_stat_user_indexes подтверждает использование нового и отсутствие сканов у удалённого. Полнотекст - Bitmap Index Scan на GIN и время в пределах лимита. Это методология «покажи на представлении», а не «поверь на слово».

← Предыдущая44-engine-securityСледующая →46-source-bridge
Footer
linuxlab-
Copyright © 2026 LinuxLab. Все права защищены.
Учебники
Цены
О платформе
Конфиденциальность и куки