Forty seconds: your first EXPLAIN ANALYZE

Timing the run: EXPLAIN ANALYZE

12 min
O que você vai aprender
  • to start the timing: EXPLAIN ANALYZE really executes the query and adds the fact beside the forecast
  • to read a plan's second bracket: actual time, rows, loops
  • to remember the main rule of that bracket: its numbers are the average for ONE launch of the node, and the full contribution is counted by multiplying by loops
  • to know what happens if you feed ANALYZE an UPDATE — and the safety rule for that case

Query blueprints no longer look like cryptography to you: cost, rows, width — you read them like a navigator's plot. But on the way down to the engine room an unpleasant thought catches up with you: all of it is forecasts. The navigator PROMISES a route for so many parrots. And how does the run actually go?

QUERY is waiting for you by the test bench — a slab with clamps, above which an ancient stopwatch with a mechanical hand dangles on a chain.

QUERY: A blueprint says how the navigator intended to travel. The timing says how the travelling went. An engineer who believes blueprints alone builds a bridge out of paper sooner or later. Wind the stopwatch.

The same machines now running — belts turning, steam venting, the chalk forecast still ghosting over them; a brass stopwatch swings from the cadet's hand beside a clicking counter wheel and chalk tally marks for repeat passes, the robotic cat on a warm pipe above and the parrot perched on the stopwatch crown.
The same route, but actually travelled: next to the forecast come the fact, the row count and the number of passes.

The second bracket: the fact beside the forecast

One word is added to the familiar EXPLAIN:

EXPLAIN ANALYZE <your query>;

With ANALYZE the machine does not stop at a forecast — it EXECUTES the query for real, with a stopwatch on every node, and prints the same blueprint with the readings written in. You have met this command in Vault-9 already; now we take its output apart figure by figure. Every node grows a second bracket:

(cost=… rows=… width=…)                     ← the navigator's forecast, parrots
(actual time=startup..total rows=… loops=…) ← the stopwatch readings, the fact
  • actual time=startup..total — built like cost (the previous lesson), only the units are honest: MILLISECONDS. The first figure is when the node handed out its first row, the second when it finished.
  • rows — how many rows the node returned IN REALITY. Beside the forecast rows of the first bracket this is the plan's main pair of numbers — we devote a whole lesson to it.
  • loops — how many times the node was launched during the run. For now keep "usually 1" in mind; a little further down you will see this number explode.

Two service lines are added at the bottom of the plan: Planning Time — how long the navigator thought, and Execution Time — how long the executor travelled.

ANALYZE is a real launch. EXPLAIN ANALYZE UPDATE … will not "show you the plan of the update" — it will UPDATE the rows and then show you the plan of what it did. The same goes for DELETE. In Vault-9 you learned the protective wrapper: BEGIN; EXPLAIN ANALYZE UPDATE …; ROLLBACK; — the plan is obtained, the changes are rolled back. Transactions are switched off in the station's sandbox, but there is nothing to fear here either: the archive is rebuilt from scratch before every run of a cell. On a live database the rule is iron: under ANALYZE only inside a with a ROLLBACK.

The estimate meets the fact

The subject is thermal sensor #388: it hangs right above the engine-room hatch, on deck J. The query collects its entire ribbon of readings by time: a full pass over the log with a filter, and a sort above it.

First the blueprint with no run, as you already know how to do:

The forecast: Seq Scan leafs through the log, Sort builds the ribbon by time. Remember the rows in Seq Scan's bracket — the navigator predicts about 360 rows. Shall we check it with the stopwatch?
The same query — but now with a run. The tree has not changed, and every node has grown a second bracket. The fact: Seq Scan returned exactly 361 rows (rows=361), having discarded 179,639 (Rows Removed by Filter), and the whole run fitted into a handful of milliseconds — Execution Time at the bottom.

Reading the stopwatch

Three observations from this fresh timing:

  1. The navigator almost guessed it. The forecast promised about 360 rows — the fact gave exactly 361. When the estimate and the fact agree, the navigator is steering by fresh charts; a discrepancy of an order of magnitude is an alarm signal, and a whole lesson of this chapter is devoted to it.
  2. actual time shows a node's character. 's startup is around zero and its total is nearly the whole run: it hands out the first row at once and then streams evenly — the stream node of the previous lesson. Sort's startup and total nearly coincide: a sort cannot hand out A SINGLE row until it has seen them all — that same dam: it accumulates and releases in one go. Two figures, and you already know which one you are looking at.
  3. The milliseconds float. Run the cell again and the times will change slightly: caches, neighbouring processes, the station's mood. But rows=361 and Rows Removed by Filter: 179639 will not shift by a single row. The fact of a run is deterministic — only the stopwatch's hand trembles, and, as you remember from the previous lesson, the navigator's forecast. Which is why an engineer quotes rows and nodes in reports, and times only by orders of magnitude.

The node that was launched forty times

In the plans above every node had loops=1: the node worked once during the run. That is not always so. Remember the report that hung: the machine launched the counting afresh for EVERY sensor. Now you will see what that looks like on the stopwatch.

We take the forty sensors of deck J — the very one whose hatch you came down through — and ask about each one: when did it last make contact?

Find SubPlan 1. The Aggregate node — that is your max() — has rows=1 loops=40: it was launched forty times and returned one row each time. Beneath it is a Seq Scan with the same loops=40 — forty full passes over the log, and the run came out dozens of times longer than for a single sensor. Skip the service block JIT at the bottom for now: the station simply compiled part of the expressions into machine code.
The bracket describes one pass out of forty: 361 rows become 14,440 handed up and seven million read through

The main rule of the bracket: multiply by loops

The numbers in the actual bracket are the AVERAGE FOR ONE launch of the node (rounded), not the sum. A node's full contribution is counted by multiplying:

  • Aggregate: rows=1 × loops=40 → forty rows for the whole run, one "last contact" per sensor.
  • on ls_readings: rows=361 × loops=40 ≈ 14,440 rows returned. And far more were LEAFED THROUGH: every launch goes through the whole log, 40 × 180,000 = 7,200,000 rows — seven million for the sake of forty answers.
  • actual time is an average per launch too: the node's full time ≈ the final figure × loops.

That is why, before judging a node by its milliseconds, an engineer looks at loops: a modest node of a few milliseconds, launched thousands of times, eats minutes — the forty seconds of the morning report are made of exactly this test, only with more sensors.

QUERY: A stopwatch does not accuse — it testifies. A node of a few milliseconds is innocent. Take a closer look at whoever launched it forty times.

The stopwatch said HOW MUCH time was spent. An engineer's next question is WHERE: what exactly was the machine leafing through all those milliseconds? The answer is measured not in rows but in pages — in the next lesson we open BUFFERS and count the input and output.

Check yourself
A timing shows (actual … rows=1 loops=40) on a node. How many rows did this node return over the whole query?
Practice: solve the tasks
Solved 0 of 1