Aurabase Logo
aurabasedocs
docs›Guides›Performance audit

Performance audit p99

Identify slow queries, understand EXPLAIN ANALYZE, indexing strategies, and the 5 anti-patterns that kill Postgres in production.

14 min read·Level advanced·Revised on Apr 15, 2026
This English text was generated automatically from the French original and has not been reviewed yet.
#
Baseline

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.

Attention

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.

clibash
# The 10 most expensive queries (total time)
aura db slow-queries --limit 10

# Connections and database size (endpoint latencies: Studio › Logs)
aura db stats
#
Slow queries

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.

top-10.sqlsql
select query, calls, mean_exec_time, total_exec_time
from pg_stat_statements
where query not like '%pg_stat_statements%'
order by mean_exec_time desc
limit 10;
#
EXPLAIN ANALYZE

Read the plan like a pro

The plan tells you why the query is slow. The 4 signals to look for:

  • Seq Scan on table > 10k rows → missing index
  • rows (estimated) ≠ rows (actual) by ±10× → obsolete stats, analyze table_name
  • Nested Loop on 100k+ lines → hash join preferred, maybe a missing WHERE
  • Materialize or Sort Method: external merge → RAM exhausted, work_mem to increase
debugsql
explain (analyze, buffers, format json)
select * from orders
where customer_id = 'abc' and created_at > now() - interval '30 days';
#
Indexes

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 null for soft delete.
  • Covering: include (...) to avoid the lookup on the table.
Attention

Each index slows down INSERT/UPDATE/DELETE (~5-15% per index). Drop unused indexes — pg_stat_user_indexes lists them with idx_scan = 0.

#
Anti-patterns

What kills Postgres in production

  1. N+1 queries — do a join or use !inner in the SDK rather than a loop
  2. Pagination by OFFSET — beyond 1000, use cursor-based (where id > last_seen)
  3. SELECT * on large tables — request only the necessary columns
  4. Long transactions — these block VACUUM and cause bloat. Keep < 1 second
  5. Non-indexed RLS Policies — if your policy uses tenant_id = auth.tenant(), index tenant_id
Last updated · Apr 15, 2026