← Back to all stories

The Night Our Database Decided to Take a Vacation

A tale of corrupted indexes, missing backups, and enough caffeine to power a small data center.

It was 2:47 AM when my phone started buzzing. PagerDuty, of course. The notification simply said: "CRITICAL: Database connection timeout - Production". Those words that every DevOps engineer dreads, especially in the middle of the night.

🚨 The Alert That Started It All

I'll never forget the exact moment. I was deep in REM sleep, probably dreaming about perfectly balanced Kubernetes clusters, when reality came crashing in. My phone wasn't just buzzingβ€”it was practically vibrating off the nightstand with the urgency of a thousand failed health checks.

Alert Log
02:47:23 [CRITICAL] PostgreSQL connection pool exhausted
02:47:24 [CRITICAL] API response time: 45000ms (threshold: 500ms)
02:47:25 [CRITICAL] Health check failed: /api/health
02:47:26 [CRITICAL] Multiple services reporting database errors
02:47:27 [CRITICAL] User-facing errors increasing: 500/min

I grabbed my laptop and connected to the VPN, still half-asleep. The Slack channel was already on fireβ€”our monitoring had caught it, but by the time I logged in, we'd already lost a significant chunk of our nightly batch processing.

"The database isn't slow. It's not responding at all. It's like it just... left."

β€” My senior colleague, equally confused at 3 AM

πŸ” The Investigation

The first thing I did was check the obvious: was the database server even running? SSH into the host, check the process... yes, PostgreSQL was running. CPU usage? Normal. Memory? Fine. Disk? Wait, what?

Shell
$ df -h /var/lib/postgresql
Filesystem      Size  Used Avail Use% Mounted on
/dev/sda1       500G  500G    0G 100% /var/lib/postgresql

The disk was full. 500GB of supposedly ample storage, completely consumed. But by what? Our database was only supposed to be around 200GB.

⚠️
Pro tip: Always set up disk space monitoring with aggressive thresholds. We had alerts at 90%, but the disk filled from 85% to 100% in less than an hour.

After some frantic investigation with du and find, we discovered the culprit: transaction logs. WAL (Write-Ahead Log) files had accumulated to nearly 300GB because our backup process had silently failed two weeks ago.

😱 The Unexpected Twist

"Okay," I thought, "let's just clean up the old WAL files and restore from backup." Simple enough, right? That's when the real horror began.

Our backup process used pg_dump to create daily backups, stored in S3. I navigated to our backup bucket, confident we'd have recent data to work with.

The last successful backup was from 14 days ago.

The backup script had been failing silently because someone (okay, it was me, three months ago) had rotated the S3 access keys and forgot to update the backup cronjob. The script was running, but every upload was failing with an authentication error. And because the error handling was... let's say "optimistic," it wasn't alerting anyone.

backup.sh (the problematic script)
#!/bin/bash
# "Production-grade" backup script

pg_dump mydb | gzip > /tmp/backup.sql.gz

# Upload to S3 (what could go wrong?)
aws s3 cp /tmp/backup.sql.gz s3://backups/$(date +%Y%m%d).sql.gz

# Clean up
rm /tmp/backup.sql.gz

echo "Backup completed!"  # This line was a lie

πŸ› οΈ The Recovery Mission

With no recent backups and a database that was essentially in cardiac arrest, we had to get creative. Here's what we did:

  1. Emergency disk space: We quickly provisioned additional EBS storage and mounted it to move some of the WAL files temporarily.
  2. Careful WAL cleanup: After consulting PostgreSQL documentation (and Stack Overflow, let's be honest), we identified which WAL files were safe to archive.
  3. Index reconstruction: Some indexes had become corrupted during the disk-full chaos. We had to rebuild them, which took another 4 hours.
  4. Data verification: We wrote scripts to verify data integrity by comparing checksums with our application logs.

By 9 AM, we had the database back online. We'd lost about 6 hours of data from our batch processing, but thankfully, most of it could be regenerated from our event logs.

πŸ“š Lessons Learned

This incident, while painful, taught us several valuable lessons that we immediately implemented:

βœ…

Backup Verification

We now have automated tests that actually restore backups weekly to verify they work.

πŸ“Š

Better Monitoring

Disk space alerts now trigger at 70%, 80%, and 90%. We also monitor backup job success explicitly.

πŸ“

Error Handling

All scripts now fail loudly. If something goes wrong, we want to know immediately.

πŸ”„

WAL Management

Implemented pg_basebackup with proper WAL archiving to prevent accumulation.

πŸ’‘
Key takeaway: Your backup is only as good as your last successful restore test. Schedule regular restore drillsβ€”they'll save you at 3 AM.

Has something similar happened to you? I'd love to hear your war stories. Drop a reaction below or reach out on Twitter. Remember: in DevOps, our failures are just future blog posts waiting to happen. πŸš€

Share this story