PG-R08performanceConfidence: 80%Reversibility: safe

Sort Spill (Temp Files from Sorts)

System views: pg_stat_statements, pg_stat_database

Overview

When Postgres sorts rows for ORDER BY, GROUP BY, or merge joins and the intermediate result exceeds work_mem, it spills to disk as a temporary file. Disk I/O is orders of magnitude slower than memory—a sort that spills is often 10-100x slower than one that fits in work_mem. Temp file spills also consume disk space and increase I/O pressure on all concurrent queries.

insightral detects spills at both the database level (pg_stat_database.temp_files) and per query (pg_stat_statements.temp_blks_written). The preferred fix is usually an index that allows an index scan to return rows in sorted order rather than requiring a sort step. Increasing work_mem is a secondary lever—it helps for queries where a sort is unavoidable.

Detection SQL

Run this query with a read-only role. No writes, no locks.

-- Databases with significant temp file usage (likely sort spills)
SELECT
  datname,
  temp_files,
  pg_size_pretty(temp_bytes) AS temp_bytes_pretty,
  temp_bytes,
  blks_read,
  blks_hit,
  round(blks_hit::numeric / NULLIF(blks_hit + blks_read, 0) * 100, 1) AS cache_hit_pct
FROM pg_stat_database
WHERE temp_files > 0
  AND datname NOT IN ('postgres', 'template0', 'template1')
ORDER BY temp_bytes DESC;

-- Per-query sort spill via pg_stat_statements (requires extension)
SELECT
  left(query, 80)          AS query_snippet,
  calls,
  temp_blks_written,
  mean_exec_time::numeric(10,2) AS mean_ms,
  total_exec_time::numeric(12,2) AS total_ms
FROM pg_stat_statements
WHERE temp_blks_written > 0
ORDER BY temp_blks_written DESC
LIMIT 20;

Fix SQL

Replace placeholders in <angle brackets> with values from the detection output before running.

-- Increase work_mem for the specific session or role (not globally)
SET work_mem = '64MB';  -- session-level, reset on disconnect

-- For a specific role that runs heavy sorts
ALTER ROLE <analytics_role> SET work_mem = '128MB';

-- Add an index to avoid sort altogether
CREATE INDEX CONCURRENTLY idx_<table>_<sort_column>
  ON <table> (<sort_column>);

Worked example

Analytics role runs nightly GROUP BY aggregations over 6M rows. temp_bytes = 14 GB over 24 hours. Mean query time: 8.2 seconds. After adding a composite index on (user_id, created_at) and setting ALTER ROLE analytics_role SET work_mem = '128MB', temp_bytes dropped to 200 MB and mean query time to 0.9 seconds.

Common false positives

  • Intentional large batch exports or ETL jobs that always spill by design — consider using a dedicated read replica with higher work_mem for these.
  • Temp files from hash joins (pg-r09) show up in the same counters — check EXPLAIN to distinguish sort spills from hash spills.

Check for sort spill (temp files from sorts) across your whole estate.

Insightral runs this rule — and 98 more — continuously inside your private cloud, with read-only credentials and nothing leaving your network.

Request early access

Last updated: 2026-05-13 · View all published rules