One Index That Fixed a 5-Second Query
1 min read
The query
SELECT date_trunc('day', created_at) as day,
COUNT(*) as count
FROM events
WHERE project_id = $1
AND created_at >= $2
GROUP BY 1
ORDER BY 1;
The problem
EXPLAIN ANALYZE revealed a sequential scan on the events table — 2M rows scanned for a single project’s data. The table had an index on created_at but not on (project_id, created_at).
The fix
CREATE INDEX CONCURRENTLY idx_events_project_date
ON events (project_id, created_at);
Before: ~5000ms. After: 8ms. A 625x improvement.
What I learned
Composite indexes are underrated. A single-column index on project_id would have helped, but the composite (project_id, created_at) covered the entire query — PostgreSQL never touches the table, just the index (index-only scan).
Always check EXPLAIN ANALYZE before adding an index. If your query pattern filters by A and sorts/aggregates by B, a composite (A, B) index is likely the answer.