agentsclimarketplace

Azpg explain analyze

Skill lestermarch/postgres-ai-experts/skills/azpg-explain-analyze

Composable AI agents and skills for operating Azure Database for PostgreSQL Flexible Server - PostgreSQL can be used for everything.

Install
npx -y skills add lestermarch/postgres-ai-experts --skill azpg-explain-analyze

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

3 things to look at

  • 20 days oldThe repository was created 20 days ago. New is not bad, but a brand new repository carrying a familiar-sounding name is the shape a typosquat arrives in, and there has been no time for anyone else to find a problem with it.
  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Read and interpret PostgreSQL query plans on Azure Database for PostgreSQL Flexible Server. Use this skill whenever the task involves EXPLAIN or EXPLAIN ANALYZE, understanding why a query is slow, reading a plan (seq scan vs index scan, nested loop vs hash vs merge join, sort/hash spills to disk), spotting bad row estimates or stale statistics, interpreting BUFFERS / shared hit vs read, finding the expensive node, or deciding whether a query needs an index, a rewrite, more work_mem, or an ANALYZE — even when the user just pastes a plan and asks "why is this slow?" or "what does this EXPLAIN mean?". Read-only: this skill diagnoses, it does not mutate.

SKILL.md

8.2 KB, as published. Nobody here has run it

Reading query plans on Azure Database for PostgreSQL Flexible Server

A query plan is the optimizer's chosen strategy. Reading it well means finding the one node that dominates the time, understanding why the planner chose it (usually its row estimate), and deciding the fix: an index, a rewrite, updated statistics, or more memory.

This skill is read-only and auto-runnable. EXPLAIN builds a plan without executing; EXPLAIN (ANALYZE, BUFFERS) does execute the query — safe for SELECT, but for INSERT/UPDATE/DELETE it performs the write, so wrap those in a transaction you roll back (see Safety protocol). It never creates indexes or changes config — for that, hand off to azpg-index-selection or azpg-config-tuning.

Deep node-by-node reference is in reference.md; the Flexible-Server timing caveats (managed I/O, shared buffers vs storage, remote latency) are in azure-constraints.md.

Live instance context (dynamic injection)

PGCONN is a libpq connection string. All read-only.

  • Postgres major version (plan/option availability): !psql "$PGCONN" -tAc "SHOW server_version;" 2>/dev/null || echo "(unknown — ask for connection details)"
  • Memory that governs sort/hash spills (Sort Method: external merge = spilled): !psql "$PGCONN" -tAc "SHOW work_mem;" 2>/dev/null || echo "(unknown)"
  • Tables with stale stats (autovacuum hasn't analyzed recently → bad estimates): !psql "$PGCONN" -tAc "SELECT relname, last_analyze, last_autoanalyze FROM pg_stat_user_tables ORDER BY n_mod_since_analyze DESC NULLS LAST LIMIT 5;" 2>/dev/null || echo "(unknown)"

When to use this skill

Trigger on: "why is this query slow?", "what does this EXPLAIN mean?", "read this plan", "is this using my index?", "why a seq scan / nested loop?", "the estimate is way off", "is it spilling to disk?", "interpret BUFFERS". If the conclusion is "add an index" hand off to azpg-index-selection; if it's "raise work_mem / tune the server" hand off to azpg-config-tuning; for aggregate slow-query forensics across the whole workload use azpg-stat-diagnostics.

Decision flow

All steps are read-only.

  1. Capture the real plan. Run EXPLAIN (ANALYZE, BUFFERS) — never optimize from EXPLAIN alone (estimates ≠ reality). Use scripts/explain_query.sql for the safe rollback wrapper on writes.
  2. Find the dominant node. Read inside-out / bottom-up; find where actual time × loops concentrates. That node is the target — ignore cheap ones.
  3. Check the estimate. Compare rows= (estimated) vs actual rows. A large gap (e.g. 10×+) means the planner is flying blind → stale stats, correlated predicates, or a non-sargable expression.
  4. Classify the problem using the table below and reference.md.
  5. Route the fix — index (azpg-index-selection), rewrite (here), stats (ANALYZE), or memory/config (azpg-config-tuning).

Symptom → likely cause → fix

In the planLikely causeFix / route
Seq Scan on a big table with a selective filterno usable index, or predicate not sargableindex it → azpg-index-selection; make predicate sargable (below)
rows estimate ≫ or ≪ actual rowsstale statistics / correlated columnsANALYZE <table>; consider CREATE STATISTICS for correlations
Nested Loop with high loops on inner sideplanner underestimated outer rowsfix the estimate (stats); an index on the inner join key helps
Sort Method: external merge Disk: …kBwork_mem too low for this sortraise work_mem (session or server) → azpg-config-tuning
Hash Batches: >1 / Disk Usage on Hashhash spilled — work_mem too lowsame as above
Rows Removed by Filter: largereading rows then throwing them awayindex the filter, or a partial index
Heap Fetches: high on an Index Only Scanvisibility map staleVACUUM <table>
Index Scan but still slowlow selectivity or wide heap fetchescovering index, or accept the scan
Big gap between planning time and executionmany partitions / complex plancheck partition pruning, generic vs custom plans

Making a predicate sargable (common rewrites)

The planner can only use an index when the indexed column appears bare on one side. Wrapping it in a function or arithmetic disables the index unless a matching expression index exists.

Non-sargableSargable rewrite
WHERE date(created_at) = '2024-01-01'WHERE created_at >= '2024-01-01' AND created_at < '2024-01-02'
WHERE lower(email) = $1keep it, but add an expression index ON t (lower(email))
WHERE col + 0 = $1 / WHERE col * 2 > $1move arithmetic to the constant side
WHERE col LIKE '%foo%'trigram GIN (pg_trgm) → azpg-index-selection
WHERE col::text = $1compare in the column's native type

Reading BUFFERS

shared hit = pages served from PostgreSQL's shared buffers (fast, in memory). shared read = pages fetched from storage. On Flexible Server, read pages come from managed remote storage, so a high read count with low hit is a strong slowness signal — the node is I/O-bound. See azure-constraints.md for why local timing intuition can mislead on managed storage.

EXPLAIN options cheat-sheet

  • ANALYZE — actually run it, show real times/rows. Executes writes.
  • BUFFERS — page hits/reads/dirtied; pair with ANALYZE.
  • FORMAT JSON — machine-readable, good for diffing plans.
  • SETTINGS — show non-default planner GUCs in effect (PG12+).
  • WAL — WAL volume for write statements (PG13+).
  • VERBOSE — output columns and schema-qualified names.

Safety protocol

  1. Reads run automatically. EXPLAIN and EXPLAIN (ANALYZE, BUFFERS) on a SELECT change nothing.
  2. ANALYZE executes the statement. For INSERT/UPDATE/DELETE, never run a bare EXPLAIN ANALYZE. Use the rollback wrapper:
    BEGIN;
    EXPLAIN (ANALYZE, BUFFERS) UPDATE ...;
    ROLLBACK;
    
    scripts/explain_query.sql does exactly this.
  3. Don't mutate to "fix" it here. This skill diagnoses. Index/config/DDL changes are handed to the write skills, which apply their own guarded, dry-run flow.
  4. ANALYZE <table> (statistics refresh) is a safe, non-destructive maintenance command — recommend it freely when estimates are stale.

Bundled files

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.