Incident Library

IL-0497 Buttondown · 2026-03-31database

Buttondown's database ran out of connections twice in one day and requests failed while health checks mostly passed

On 31 March 2026 Buttondown's database reached the maximum number of connections it was configured to accept, and once that ceiling was hit, new requests could not acquire a connection and failed. The database itself stayed healthy. The connection limit had been set a long time before and had not been revisited as Buttondown's traffic patterns changed. The health check endpoint returned a static 200 without checking that the process could get a database connection, and the external checker's requests reached workers that already held connections, so monitoring did not alert and the outage was found through user reports and manual observation.

In the first outage the team identified the connection exhaustion and killed active queries to free connections, with about seven minutes of downtime. The same root cause came back later that day, and this time no connection was free to kill queries with, so the database had to be restarted to force-kill the running queries; that outage lasted about thirteen minutes. Buttondown says the second outage lasted nearly twice as long because no connection was reserved for administrative access.

Severity
–
Scope
Buttondown's service: requests that needed a database connection failed, in two outages of about seven and about thirteen minutes
Users affected
–
Region
–
Time to detect
–
Time to resolve
–

Symptoms

On 31 March 2026 Buttondown was down twice, for about seven and about thirteen minutes. New requests could not get a database connection and failed, although the database itself was healthy: queries were fast, CPU and memory were fine and replication lag was nominal.

No automated alert fired. The health check endpoint returned 200 for most requests, because the health checker's requests happened to land on workers that already held open database connections. The team found out from user reports and its own manual observation.

In the first outage the team killed active queries to free connections. In the second outage the connection count was so saturated that the team could not connect to the database to kill queries, because the tool used in the first outage itself needed a database connection.

Symptom Connection pool exhaustionService outage

Root cause

Trigger
–

Detection

User reports and the team's own manual observation, not automated monitoring. The health check endpoint kept returning 200 for most requests, so no alert fired.

Remediation

Action items

Lessons

Buttondown notes that the tool used to recover from the first outage needed the same resource that was exhausted, which locked the team out during the second outage, and that a health check which cannot detect connection exhaustion, the most common failure mode, gives little protection. A configuration value set long ago drifted out of line with traffic because nobody reviewed it.

Sources

Related cases

Check your cluster for this

kubegap audits a Kubernetes cluster for conditions like the ones in this case, Database among them, read-only, from Claude Code or Codex. Its checks cite public reports like this one.

For AI tools and scripts

The API needs no key. The MCP server offers search_incidents, get_incident, list_incidents, get_topic and vocab; both are explained on API and MCP.