Free tool

See where a slow Postgres query spends its time.

Paste what EXPLAIN (ANALYZE, BUFFERS) printed — text or JSON. Get the plan as a tree with each node’s own time, the nodes ranked by cost, and a plain-English list of what looks wrong: scans that read millions of rows to keep a few, sorts that spilled to disk, estimates that were off by 40×.

plan.txt
1.8 KBtext plan · ANALYZE
Query plan.html

Execution 813 ms · planning 0.412 ms · 7 plan nodes · slowest: Seq Scan on orders o (513 ms, 63%) · 3 warnings

Publishes this report on Comma: one URL for the plan you can drop in the incident channel or the PR, restricted to named people or your company’s email domain if you like, comments pinned to the exact node, and the plan after your index kept as a revision you can diff. Free, no card.

How the plan is read

  • Every line with an arrow (->) is a node; its indentation says which node it feeds. Lines beneath a node — Filter, Index Cond, Rows Removed by Filter, Buffers, Sort Method — are kept as its details.
  • Self time is actual time × loops minus the same for the node’s children. That is the number the “where the time goes” table ranks by, as a share of Execution Time.
  • Misestimates compare rows= in the cost estimate with rows= in the actuals. Ten times off or worse is flagged: that is when the planner starts choosing the wrong join or scan.
  • Disk spills come from Sort Method: external merge Disk: … and Batches: greater than 1 on a Hash — both mean the operation outgrew work_mem.
  • InitPlan, SubPlan and CTE subtrees are labelled; never executed nodes stay in the tree at zero.

Getting the plan

WhereRun
psqlEXPLAIN (ANALYZE, BUFFERS) SELECT …;
psql, to a file\o plan.txt then the EXPLAIN, then \o
JSONEXPLAIN (ANALYZE, BUFFERS, FORMAT JSON) SELECT …;
Shellpsql -XAt -c "EXPLAIN (ANALYZE, BUFFERS) …" > plan.txt
A write, safelyBEGIN; EXPLAIN (ANALYZE) UPDATE …; ROLLBACK;
Slow queries in prodauto_explain.log_min_duration = '500ms'

auto_explain writes the same text plan to the server log for every query slower than the threshold — paste it here as-is. On PostgreSQL 18, BUFFERS is included with ANALYZE by default.

When the plan needs a second pair of eyes

A query plan in a Slack message loses its indentation, and a screenshot of psql can’t be searched or commented on. When the person who has to approve an index, a work_mem change or a rewrite isn’t the one who ran the query, send them one link: the plan as a tree, the slow node at the top, and room to comment on the node in question. Publish the plan again after the fix and the report keeps both as revisions, so the before and after sit side by side. An agent can do it too — Comma’s MCP server lets Claude or Cursor publish the plan it just analysed. More on reading plans in what EXPLAIN ANALYZE shows.

Questions

How do I visualize a PostgreSQL EXPLAIN ANALYZE plan?
Run EXPLAIN (ANALYZE, BUFFERS) followed by your query in psql, copy everything it prints — the QUERY PLAN header and row count can stay — and paste it here. You get the plan as a tree with each node's own time, the nodes ranked by where the time goes, and warnings for sequential scans that throw most rows away, sorts and hashes that spilled to disk, and row estimates that were off by 10× or more. Download it as one HTML file or publish it as a link.
Text or JSON — which output should I paste?
Either. The default text plan works, and so does EXPLAIN (ANALYZE, FORMAT JSON), including psql's version with a + at the end of every line. pgAdmin and DBeaver copies, where each line is wrapped in quotes, are read too. XML and YAML formats are not; switch to text or JSON.
What is “self time” and why is it different from actual time?
The actual time PostgreSQL prints for a node includes everything beneath it — a Hash Join's time contains both of its scans. Self time subtracts the children, so it is the time spent in that node alone. It is also multiplied by loops: a node that took 0.05 ms but ran 20,000 times inside a nested loop cost a full second. Ranking by self time is how you find the node actually worth fixing.
How are parallel plans counted?
Under a Gather or Gather Merge, PostgreSQL reports each node's time as an average per process and counts the leader and every worker in loops. The viewer divides by the number of processes (Workers Launched + 1), so a Parallel Seq Scan shows the wall-clock time it took, not three times it.
Does it work without ANALYZE?
Yes, but a plain EXPLAIN only has estimates. The viewer then ranks nodes by planner cost, which is a unit of estimated work, not milliseconds. EXPLAIN ANALYZE actually runs the query, so for an INSERT, UPDATE or DELETE wrap it in BEGIN; … ROLLBACK; to keep the changes from sticking.
Can I post the plan on a pull request or in a ticket?
Yes. “Copy Markdown” gives the top nodes by self time as a GitHub-flavored table, with the warnings beneath it, ready for a PR comment, an incident channel or a Jira ticket. For the full tree, publish the report and link it.
Is my query plan uploaded?
No. The plan is parsed and the report is built in your browser. Plans can contain table names, filter values and parameters, so nothing leaves your machine unless you click “Get a shareable link” and publish the report to Comma — and then you choose who can open it.

Related: what EXPLAIN ANALYZE is · JSON viewer · Terraform plan viewer · diff two plans · what a flame graph is · all free tools