Сорок секунд: первый EXPLAIN ANALYZE

Куда ушло время: BUFFERS

12 min
What you'll learn
  • мерить работу запроса страницами, а не секундами: EXPLAIN (ANALYZE, BUFFERS)
  • отличать shared hit (страница из кеша) от read (страница с диска)
  • находить «прочитано зря» в строке Rows Removed by Filter
  • видеть контраст маршрутов: полный проход — мегабайты, точечный запрос по ключу — килобайты

Хронометраж показал тебе, сколько стоит каждый узел маршрута. Но одна загадка осталась: тот же запрос по тому же плану идёт то быстро, то заметно медленнее — как будто у машины бывает настроение.

КВЕРИ ведёт тебя дальше по машинному отделению, туда, где гул ровнее и воздух холоднее. Вдоль стен — стеллажи с одинаковыми контейнерами. Весь архив станции нарезан на такие блоки: страницы по восемь килобайт.

КВЕРИ: У машины не бывает настроения. Бывает кеш. Секундомер меряет погоду: тёплая страница — миллисекунды, холодная — жди диск. Работу меряют не секундами. Работу меряют страницами.

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

A conveyor of identical metal page-plates runs through the compartment and out of frame both ways; at its end the cadet holds a tiny tray with a few shavings, warm glowing plates feeding from a brass magazine on the left, frost-covered ones hauled on chains from a cold vault on the right, the robotic cat walking the line and the parrot sitting on the tray.
Work is measured in pages: nearly twelve megabytes churned to hand back a few dozen kilobytes.

Страницы: валюта ввода-вывода

Postgres никогда не читает «одну строку». Таблица хранится страницами по 8 КБ, и страница — минимальная единица чтения: нужна тебе из неё одна строка или все сто двадцать — со стеллажа поднимается весь контейнер. Журнал ls_readings на сто восемьдесят тысяч строк — это 1513 страниц, примерно по сто двадцать строк в каждой.

Между диском и исполнителем стоит буферный кеш — область памяти, где живут недавно тронутые страницы. Отсюда два тарифа на одну и ту же страницу:

  • shared hit — страница нашлась в кеше. Быстро: это просто память.
  • read — страницы в кеше не было, пришлось поднимать с диска. Медленнее — иногда на порядки.

Флаг BUFFERS дописывает эти счётчики к каждому узлу плана:

EXPLAIN (ANALYZE, BUFFERS) <твой запрос>;

План превращается в карту ввода-вывода: видно не только, где узел потратил время, но и сколько страниц он для этого перетаскал.

Открой такую карту для знакомого маршрута — полного прохода по журналу. Условие нарочно узкое — показания со статусом warn, их в журнале всего два процента, — но индекса по status в журнале нет, и у машины один маршрут: поднять со стеллажей каждую страницу.

Query work is measured in pages: shared hit from cache, read from disk, temp spilled to a file
Три строки под сканом — суть всего урока: Rows Removed by Filter: 176400 — столько строк прочитано и выброшено; Buffers: shared hit=1513 — журнал поднят со стеллажей целиком, страница за страницей.

Читаем накладную

Разбери три строки под Seq Scan — накладную этого рейса:

  • Filter: (status = 'warn') — условие, которое исполнитель проверял на каждой строке.
  • Rows Removed by Filter: 176400 — строки, которые прочитали и выбросили. Запросу нужны 3600 строк, а прочитан весь журнал: девяносто восемь из каждых ста поднятых строк уехали в мусор. Это и есть «прочитано зря» — главный счётчик расточительности маршрута.
  • Buffers: shared hit=1513 — все 1513 страниц журнала. Умножь на восемь килобайт: запрос перелопатил почти двенадцать мегабайт, чтобы отдать тебе несколько десятков килобайт результата. Заодно разгадка ценника из позапрошлого урока: 3763 попугая скана — это 1513 страниц по цене страницы плюс построчная работа процессора над ста восемьюдесятью тысячами строк.

Заметь: счётчика read в выводе нет вовсе. Журнал только что посеян песочницей и целиком горяч — каждая страница нашлась в кеше, поэтому весь проход уложился в считаные миллисекунды. На так везёт не всегда: первый утренний запрос застаёт кеш холодным, те же 1513 страниц превращаются из hit в read — и время подскакивает в разы. План тот же, работа та же — погода другая. Вот почему инженер сравнивает маршруты по страницам, а не по секундомеру.

Мелкая деталь: у блока Planning: есть собственные Buffers — это страницы, которые тронул сам , пока сверялся с каталогом. К маршруту они не относятся.

Точечный рейс

Теперь противоположный полюс. Единственный , который в журнале уже есть, — индекс reading_id. Спроси одну конкретную строку — и штурман проложит маршрут не по стеллажам, а по лестнице индекса: с этажа на этаж, каждый шаг — одна страница, и в конце — единственная страница журнала, где лежит сама строка.

В ячейке две команды — приём, который ты будешь видеть в этом курсе постоянно: первая выполняет сам запрос — и штурман, готовя его, заранее пролистывает свои страницы каталога, — вторая снимает карту с того же запроса. Собственные Buffers узла от прогрева не меняются, но без него под картой вырос бы блок Planning: с тремя десятками служебных страниц. Ты уже знаешь, что к маршруту они не относятся, — а чистая накладная, где четыре страницы и ничего лишнего, красноречивее.

Index Scan — поиск по индексу вместо полного прохода — и Buffers: shared hit=4: три шага по лестнице индекса и одна страница журнала. Четыре страницы вместо 1513.

Мегабайты против килобайт

Положи две накладные рядом. Полный проход: 1513 страниц — почти двенадцать мегабайт перелопаченного архива. Точечный рейс: четыре страницы — тридцать два килобайта. Маршруты к одной и той же таблице различаются по вводу-выводу почти в четыреста раз.

И вот что важно: на глаз оба запроса «мгновенные» — миллисекунды там, доли миллисекунды тут; горячий кеш прячет разницу. Секундомер здесь почти слеп. А карта ввода-вывода — нет: она показывает работу, которую машина совершает при любой погоде. Запрос, который таскает мегабайты ради килобайт, — это счёт, который однажды придёт: холодным утром, на выросшем журнале, в толчее соседних запросов.

КВЕРИ: Плохой маршрут на тёплом кеше — как течь в трюме при штиле. Незаметно. Ровно до первого шторма.

Одна деталь напоследок. В первой строке карты полного прохода штурман обещал несколько тысяч строк — и почти угадал: приехало ровно 3600. Пока оценка и факт дружат. В следующем уроке ты увидишь, что случается с маршрутами, когда оценка с фактом ссорятся, — и почему эта пара — главная на всём чертеже.

Check yourself
В карте полного прохода стоит строка Buffers: shared hit=1513. Значит ли это, что запрос прочитал с диска 1513 страниц?
Practice: solve the tasks
Solved 0 of 1