IL-0456 Inflab · 2022-01-03databaseaws
Inflearn went down six times during a January 2022 free-course event as slow queries piled up on Aurora PostgreSQL
Inflearn runs as a monolithic Node.js project on ECS Fargate, 10 to 30 instances depending on traffic, with Aurora PostgreSQL 10.16 as one writer and 2 to 7 readers. Most reads go to the readers and writes to the writer, but because services do not have their own databases, a problem in one part spreads to the whole service. The event, 125 courses worth 5,016,000 won in total offered for free, went far beyond expectations: more than 10,000 retweets, shares on many IT communities, traffic 5 to 20 times normal and 76,730 new sign-ups in 7 days. The first outages were handled with quick fixes: an index for the interested-companies query, a changed cache reset and regeneration interval, moving read queries off the writer, index-friendly rewrites or switching features off, dropping the course description column from select * queries, and splitting IN lists into batches of 20 IDs run with Promise.all.
The 17 January outage, slow queries on a table of under 10,000 rows, led the team to check tuple statistics on the writer DB. The course table had 99% dead tuples. Autovacuum should have been working on it, so the team ran VACUUM by hand and then watched the dead tuple rate, which climbed again very fast. The course table kept the number of students as a column, and every enrollment ran an UPDATE that added 1 to it. Because PostgreSQL's MVCC writes a new tuple on every update and leaves the old one as a dead tuple, and the course table's rows carried large columns such as the HTML course description, the frequent updates produced large dead tuples in bulk, made worse by the surge of students enrolling in 100 or more courses at once. That bottleneck slowed every query on the course table and was the root cause throughout the event.
The team chose to move the large course description column into a separate table rather than move the student count column, which would have meant working out its effect on the other existing features. After that, the event caused no further outages.
- Severity
- –
- Scope
- Inflearn was unusable at peak hours on 3, 4, 7 and 17 January 2022, six outages in total, including 1 h 30 min and 2 h on 3 to 4 January and 3 h on the morning of 4 January
- Users affected
- –
- Region
- –
- Time to detect
- –
- Time to resolve
- –
Symptoms
During a 100% discount event that made 125 courses free, Inflearn received 5 to 20 times its normal traffic, and on four days, 3, 4, 7 and 17 January 2022, the service became unusable around 9 a.m. and 10 p.m. There were six outages in all.
They showed as large numbers of slow queries on the Aurora PostgreSQL 10.16 database and, at times, writer CPU above 90%.
On 3 January, from 20:30 to 22:00, slow queries from the course pages' interested-companies lookup, part of which had no index, quickly used up the connections of the Node processes; later requests waited until the processes were killed, and although ECS added new processes, every other query those processes were running was cancelled, so the whole service stopped. From 23:30 to 01:30 the writer DB's CPU stayed above 90%: cache data moved from Redis into the database lived in a single large JSON row, cache resets and regeneration under the higher load hit that row at once and led to deadlocks, and read queries not yet moved to the readers added load.
On 4 January from 09:00 to 12:00, and again in the afternoon, course lookups were the slow queries, all of the top nine. Most could not use indexes, using lower("column") = lower(parameter) or negative conditions such as != and NOT IN, so tens to hundreds of full table scans ran at once, and select * pulled a 10 KB course description column of HTML into every course query. Scaling the database to 8 r5.8xlarge instances bought only a few tens of minutes. Finding the slow queries took under 30 minutes, but fixing them took 3 hours because every query was wrapped in functions, hooks and custom events, and the code had no types and no tests. On 7 January from 21:00 a course query with where id in (...) ran full table scans once the list passed 100 IDs, as students put all 125 courses in the cart and checked out at once.
Before the second event the team upgraded PostgreSQL from 10.16 to 11.13, set a 5-second query timeout, raised max connections and tuned queries over 2 seconds, and the busiest time of that week, 09:00 on Saturday 15 January, passed without trouble. At 22:00 on 17 January RDS metrics began to swing, and at 22:30 the sixth outage started, with slow queries even on a course table of fewer than 10,000 rows. Because every bottleneck so far had shown up as slow queries on the reader DBs, the team had been watching only the readers.
Root cause
Root cause Capacity exhaustion
- Trigger
- A 100% discount event on 125 courses, from 3 to 17 January and 18 to 24 January 2022, went viral and brought 5 to 20 times the usual traffic.
Detection
The team followed the outages through hourly transaction counts, RDS metrics, monitoring and slow query alerts.
Remediation
- Added an index to the interested-companies query on the course page
- Changed how often the cache data stored in the database was reset and regenerated
- Moved the read queries that loaded the writer DB to the reader DBs
- Scaled the database to 8 r5.8xlarge instances, which bought only a few tens of minutes
- Rewrote slow course queries or added indexes so they use indexes, and switched off features whose queries could not be fixed
- Removed the large course description column from the select * course queries
- Split the IDs of the IN clause into batches of 20 run with Promise.all
- Upgraded PostgreSQL from 10.16 to 11.13
- Set a 5-second query timeout
- Raised max connections
- Ran VACUUM by hand on the course table
- Moved the large course description column into a separate table
Action items
- Change the structure in which one table's failure spreads to the whole service
- Improve the table and cache structures that are hard to tune
- Make the SQL code easier to follow
- Improve code that gives no confidence to change, such as code without types or test code
- Build a stable batch environment
- Keep a monolith of reasonable size and separate the domains through which failures spread most
Lessons
Because every database bottleneck appeared as slow queries on the reader DBs, watching only the readers hid the root cause on the writer. In PostgreSQL, frequent updates to a table with large columns produce large dead tuples in bulk and slow every query on that table. Before the second event the team could not run a meaningful performance test because the first week showed that almost any page could fail under load. The event exposed high sensitivity to failure in every area, one table's failure spreading to the whole service, hard-to-tune table and cache structures, unintuitive SQL code, code without types or tests, no stable batch environment, and many hidden failure points not yet found.
Sources
Related cases
IL-0553CircleCI's orchestration database filled its transaction log volume, and no workflows or jobs started for 50 minutesCircleCI · 2026same root cause · shared Database, PostgreSQL vacuum
IL-0550Bitbucket Cloud's database slowed until connection pools saturated and requests timed out, leaving Pipelines pendingAtlassian · 2024same root cause · shared Database, PostgreSQL vacuum
IL-0211A Mandrill PostgreSQL shard shut down at transaction ID wraparound and email sending was impaired for about 40 hoursMailchimp · 2019same root cause · shared Database, PostgreSQL vacuum
IL-0450Oracle RAC commits behind order APIs kept waiting over 0.1 s on log file sync while average host CPU had headroomToss Securities · 2026same root cause · shared Database
IL-0379InfluxDB Cloud queries in Azure West Europe slowed and timed out as storage pods fell behind and kept restartingInfluxData · 2026same root cause · shared Database
IL-0309Preset's RDS PostgreSQL 14.7 to 17.9 upgrade in US East 1 ran out of storage and took Superset down for six hoursPreset · 2026same root cause · shared Database
IL-0435Error rates rose as Aurora MySQL killed connections and declined queries to avoid OOM while load metrics stayed flatCyberAgent · 2026same root cause · shared Database
IL-0497Buttondown's database ran out of connections twice in one day and requests failed while health checks mostly passedButtondown · 2026same root cause · shared Database
IL-0288ZooKeeperWait rose to thousands of seconds on three redundant ClickHouse instances, degrading a CDN77 data pipelineCDN77 · 2026same root cause · shared Database
IL-0350Hosts in an IONOS TXL cluster lost connectivity when an InfiniBand fabric resweep failed on a full multicast tableIONOS Cloud · 2026same root cause · shared Database
IL-0305Replicas of one Intercom Vitess shard hit 100% CPU and fell up to 2.2 hours behind, serving stale inbox dataIntercom · 2026same root cause · shared Database
IL-0390PostHog US Cloud event ingestion slowed to delays of up to 2 days as writes to the Persons Postgres table stalledPostHog · 2025same root cause · shared Database
IL-0100PostHog's feature flags pods were packed onto Kubernetes nodes that were too small for their CPU requests, and CPU saturation above 90% caused three outages in ten days through Redis overload, connection pool exhaustion and slow queriesPostHog · 2025same root cause · shared Database
IL-0287One overloaded shard of MongoDB's internal monitoring database delayed Atlas metrics, alerts and cluster operationsMongoDB · 2025same root cause · shared Database
IL-0557Database transaction locks piled up on Storj's US1 satellite, and uploads and downloads failed with timeouts for 2 hoursStorj · 2025same root cause · shared Database
IL-0551Bitbucket Cloud's overloaded primary database failed over, and the website, API and Pipelines were slow for almost a dayAtlassian · 2025same root cause · shared Database
IL-0298Wikimedia edits failed for 38 minutes when sessionstore Cassandra nodes in both data centres crashed on full disksWikimedia Foundation · 2025same root cause · shared Database
IL-0312Gadget's main Postgres did not start again after a restart to raise max_connections, and the platform went downGadget · 2024same root cause · shared Database
IL-0544Wasabi's database cache stopped responding when its queueing service filled up, and S3 failed in all regionsWasabi · 2024same root cause · shared Database
IL-0447An every RDS for MySQL instance slowed badly when its gp2 EBS BurstBalance ran out and IOPS fell to the baselineevery · 2024same root cause · shared Database
IL-0375MongoDB Atlas cluster changes and alerts lagged for hours after a control plane metadata migration congested a databaseMongoDB · 2024same root cause · shared Database
IL-0110A Grafana Agent pod's high memory use and crash-looping Hangfire pods drove a Kubernetes node into system OOM and NotReady, twice in one dayDFDS · 2024same root cause · shared Database
IL-0270Discord's ScyllaDB authentication cluster overloaded during an OS upgrade, and logins failed for about 50 minutesDiscord · 2023same root cause · shared Database
IL-0202A burst of queries and database writes exhausted connection pools and memory on Honeycomb's ingest hosts, and Kubernetes crash-loop back-off kept failed hosts offline so ingest capacity stayed too low to recoverHoneycomb · 2023 · sev1same root cause · shared Database
IL-0386After a move to Google Cloud, Postgres connection timeouts and memcached i/o timeouts spiked on GKE nodesincident.io · 2023same root cause · shared Database
IL-0326A retention clean-up job filled the disk of MailPace's managed Postgres, which stayed offline for just over two hoursMailPace · 2023same root cause · shared Database
IL-0546Buildkite jobs could not go to newly connected agents once agent IDs passed a 32-bit integer foreign key's limitBuildkite · 2023same root cause · shared Database
IL-0500UToronto JupyterHub logins and server starts returned 500 errors when the hub's sqlite database disk filled up2i2c · 2022same root cause · shared Database
IL-0301Basecamp 3's primary database went into total gridlock at the top of the hour and Basecamp 3 went down twice37signals · 2022same root cause · shared Database
IL-0206Consul agents in GitLab.com's GKE pods were repeatedly OOM-killed, so the Rails pods lost database replica discovery and sent every query to the primaryGitLab · 2022same root cause · shared Database
IL-0302Peak load on GitHub's mysql1 MySQL cluster maxed out its database proxy's connections and stopped writes in March 2022GitHub · 2022same root cause · shared Database
IL-0212Many Slack users could not connect because a Vitess keyspace for channel membership was overloaded by cache-miss queriesSlack · 2022same root cause · shared Database
IL-0321GCP snapshots on all Patroni nodes stalled GitLab.com's primary PostgreSQL disk, Patroni failed over and 5xx spikedGitLab · 2021same root cause · shared Database
IL-0496Ably's Cassandra hit 100% CPU in every region and queries timed out globally, and the load returned on two more eveningsAbly · 2020same root cause · shared Database
IL-0443A Repro Aurora MySQL reader restarted unexpectedly and writer insert latency rose about sevenfold for a day or moreRepro · 2019same root cause · shared Database
IL-0037Image registry rate-limited a whole region after a crash-looping DaemonSet with imagePullPolicy Always pulled through three NAT instancesDatadog · 2019same root cause · shared Database
IL-0297Mezmo users could not sign in and log indexing lagged after the auth cache's Redis servers were OOM killedMezmo · 2019same root cause · shared Database
IL-0064CPU-heavy pods packed onto one Google Cloud node drove it to kernel panics, and the crash moved from node to node until the site was downMoonlight · 2019same root cause · shared Database
IL-0300Basecamp 3 went read-only for almost five hours when its events table reached the ceiling of 2,147,483,64737signals · 2018same root cause · shared Database
IL-0334Instapaper's RDS MySQL bookmarks table hit the 2TB ext3 file size limit, inserts failed and the service went downInstapaper · 2017same root cause · shared Database
IL-0333A downsized RDS PostgreSQL database maxed out its CPU at US peak and failing ELB health checks took buildkite.com downBuildkite · 2016same root cause · shared Database
IL-0282DynamoDB in US-East reached about 55% request errors as storage servers overloaded its metadata serviceAmazon Web Services · 2015same root cause · shared Database
IL-0383PagerDuty's shared Cassandra cluster degraded during a repair, delaying notifications and losing incoming eventsPagerDuty · 2014 · sev1same root cause · shared Database
IL-0320Postmark's primary MySQL disk filled but MySQL kept running, so MySQL-MMM never failed over and sending was delayedPostmark · 2011same root cause · shared Database
Check your cluster for this
For AI tools and scripts
- This case as JSON: https://incidentlib-api.benchlet.com/cases/IL-0456
- The cases most like it: https://incidentlib-api.benchlet.com/cases/IL-0456/similar
- MCP server (Streamable HTTP, no key):
https://incidentlib-mcp.benchlet.com/mcp
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.