Performance audit p99
Identify slow queries, understand EXPLAIN ANALYZE, indexing strategies, and the 5 anti-patterns that kill Postgres in production.
Measure before optimizing
An audit starts with numbers. The Studio exposes p50/p95/p99 by endpoint and by query. Capture a 7-day baseline before any change.
Roadmap — not available. The aura db slow-queries and aura db stats commands below are planned and not delivered yet: the CLI does not have them today. Read the same numbers in the Studio (Logs) or with the pg_stat_statements query in the next section.
pg_stat_statements in real time
The pg_stat_statements extension is enabled by default. It aggregates each normalized query with mean_exec_time, calls, rows. The Studio displays this in a sortable table.
Read the plan like a pro
The plan tells you why the query is slow. The 4 signals to look for:
Seq Scanon table > 10k rows → missing indexrows (estimated) ≠ rows (actual)by ±10× → obsolete stats,analyze table_nameNested Loopon 100k+ lines → hash join preferred, maybe a missing WHEREMaterializeorSort Method: external merge→ RAM exhausted,work_memto increase
When, what, how
- B-tree (default): equality, range, order by. 80% of cases.
- GIN: full-text search, JSONB, tables.
using gin (to_tsvector('french', body)). - GiST: geospatial (PostGIS), similarity search (pg_trgm).
- HNSW (pgvector): vector search, > 100k vectors.
- Partial: common conditions,
where deleted_at is nullfor soft delete. - Covering:
include (...)to avoid the lookup on the table.
Each index slows down INSERT/UPDATE/DELETE (~5-15% per index). Drop unused indexes — pg_stat_user_indexes lists them with idx_scan = 0.
What kills Postgres in production
- N+1 queries — do a join or use
!innerin the SDK rather than a loop - Pagination by OFFSET — beyond 1000, use cursor-based (
where id > last_seen) - SELECT * on large tables — request only the necessary columns
- Long transactions — these block VACUUM and cause bloat. Keep < 1 second
- Non-indexed RLS Policies — if your policy uses
tenant_id = auth.tenant(), indextenant_id