How an Unindexed Column Killed Our Database

A single missing index turned an 8 ms API into an 8-second nightmare.

We ran a load test before a major release. Local development handled 100 rows fine. In staging we simulated 500 users against 1.5 million rows.

Everything broke.

  • API response times jumped from 45 ms to 8,000 ms.
  • Database CPU hit 100 %.
  • Connection pools exhausted.
  • Requests timed out.

The culprit was a simple query that fetched order history by user_id and status.

Running EXPLAIN ANALYZE on PostgreSQL showed a Sequential Scan. With no index on user_id, the engine read all 1.5 million rows for every request.

At 100 concurrent requests the database scanned 150 million rows at once.

The fix took five minutes. We created a composite index on (user_id, status, created_at DESC) using CREATE INDEX CONCURRENTLY so the table stayed online.

Results:

  • Query time fell from 8,150 ms to 0.14 ms.
  • API latency dropped from 8 seconds to 12 ms.
  • CPU usage slid from 100 % to under 8 %.

Lessons learned:

  • Local tests mislead; 100 rows aren’t a proxy for a million.
  • Index foreign keys—most ORMs skip this.
  • Run EXPLAIN ANALYZE; the database points out the problem.
  • Optimize queries before buying more hardware.

Don’t scale servers first. Scale queries.

Source: https://dev.to/mia_keller_ffd2584c046ecb/how-an-unindexed-column-silently-killed-our-database-under-load-and-the-5-minute-fix-m32