Skip to content
keegan@portfolio:~$
$ cd ..

keegan@portfolio:~/projects/rds-polling-outage$ cat README.md

Case study: an RDS outage caused by polling

How a 2-second polling endpoint saturated a production RDS cluster on MSA Grid, and how I traced it to a sequential scan and cut database CPU from ~95% to ~30% within hours.

My role: Diagnosed the root cause, shipped the fix and drove the follow-ups · Software Engineer, Safety iO

The problem

MSA Grid is safety-critical: safety managers rely on it to see gas-detection alarms from workers in hazardous environments. A Datadog monitor fired on rising API errors, and the cause turned out to be the RDS/PostgreSQL cluster behind the API, not the API itself. There was no traffic spike from new users. Every open client polled one endpoint every 2 seconds, and that endpoint's query was cheap to write but expensive to run. Multiplied by the poll rate across every customer, it saturated the database.

What I did

  • Symptom: a Datadog monitor alerted on API errors; correlating it with RDS metrics showed the database, not the Lambdas, was the bottleneck.
  • Diagnosis: RDS Performance Insights ranked the top SQL by load, which pointed to a single query behind the polling endpoint; EXPLAIN ANALYZE showed it doing a sequential scan.
  • Why it hurt: each client polled every 2 seconds, so a modest per-query cost was multiplied by poll frequency and the number of customers into sustained load.
  • Fix: added a composite index matching the query's WHERE clause, and moved data that had been embedded in a larger table into its own table so the hot path reads far less.
  • Outcome: mitigated within hours; cluster CPU dropped from ~95% to ~30% and the API errors cleared.
  • Prevention: added Datadog monitors and alarms on the cluster's CPU, introduced index review in pull requests, and proposed replacing polling with WebSockets.
  • Takeaway: on polling-heavy APIs, per-request query cost multiplies by poll frequency, so it needs to be designed and monitored from day one.

Visual summary

Where the load came from

Hot path

  1. Every open client
    polls every 2 s
  2. API Gateway
  3. AWS Lambda
    polling endpoint
  4. Problem: RDS for PostgreSQL
    sequential scan · CPU ~95%

Fix & prevention

  1. Fix: Composite index
    matches the WHERE clause
  2. Fix: Split table
    hot path reads far less data
  3. Fix: Datadog monitors
    alarms on cluster CPU
Every open client polled one endpoint every 2 seconds, and each poll ran a sequential scan on RDS.

RDS cluster CPU

before
CPU utilisation: ~95%
after
CPU utilisation: ~30%
Cluster CPU before and after the fix, mitigated within hours.