I’ve used EXPLAIN ANALYZE for years without realizing how much I was missing by not adding BUFFERS.

EXPLAIN (ANALYZE, BUFFERS)
SELECT * FROM orders WHERE user_id = 12345;

The output now includes lines like:

Buffers: shared hit=1 read=247
  • shared hit=1 → 1 page came from Postgres’ shared buffer cache
  • read=247 → 247 pages had to be fetched (from OS cache or disk)

This matters because the same query can take 0.3 ms or 30 ms depending on whether the data is hot. If you’re chasing tail latency, EXPLAIN ANALYZE alone will lie to you on a warm cache.

The takeaway

When debugging slow queries, always use BUFFERS. Run the query twice:

  1. First run shows the cold-cache cost (read is high)
  2. Second run shows the warm-cache cost (shared hit is high)

The gap between them is your storage I/O dependency. Big gap = your query is disk-bound under cold cache, which means latency spikes after restarts or cache evictions.

Annoyingly, EXPLAIN ANALYZE executes the query, so don’t run it on DELETE / UPDATE in production unless you wrap it in a transaction and roll back.