IL-0332 Travis CI · 2018-03-13database
A test run against the production database truncated every table and travis-ci.com was down for about 5.5 hours
Many days earlier, a developer had used a terminal window in a tmux session to inspect production data, with DATABASEURL set to the production database. Travis CI's tooling and processes made it hard to connect to the read-only follower, so connecting to the primary with write access was a common shortcut. On 13 March 2018 the developer ran the test suite in that window, and the Database Cleaner gem truncated all tables in production; the query was blocked for about 10 minutes and ran at 12:14 UTC. Finding this took a day, using API logs and the source IP address of the query supplied by the upstream provider.
Responders left the user-facing applications running for about 30 minutes against the nearly empty database, which created duplicate user ids. All applications were then taken offline and a replacement database was provisioned from a snapshot taken that morning; provisioning finished earlier than first estimated, the database was reattached at 17:06 UTC, and builds for private repositories were running again at 17:13 UTC. travis-ci.com had been down for about 5.5 hours, and the build backlog on the Mac and sudo-enabled infrastructure took about another 3.5 hours to clear, by 20:59 UTC. About 15 minutes of data were lost. Users who had signed in during the 30 minutes were logged in as other users after the restore, and all affected tokens were revoked by 14:22 UTC on 14 March. Travis CI reviewed its logs, contacted every customer who was technically affected, found that some had potentially had build logs exposed, and advised them to rotate credentials stored on travis-ci.com and check their build logs for sensitive information. The cron scheduler had not been restarted after the restore, so scheduled cron jobs failed to trigger.
- Severity
- –
- Scope
- travis-ci.com non-operational for about 5.5 hours from 12:14 UTC and a build backlog for about 3.5 hours more; about 15 minutes of data lost; users who signed in during the first 30 minutes were later logged in as other users, and some build logs were potentially exposed; scheduled cron jobs failed to trigger after the restore
- Users affected
- New and existing travis-ci.com users; those who signed in during the 30 minutes after the truncation were logged in as wrong users
- Region
- –
- Time to detect
- –
- Time to resolve
- 5h 30m
Symptoms
On 13 March 2018 at 12:14 UTC every table in travis-ci.com's production PostgreSQL database was truncated. Alerts fired right away, and customers reported missing account data; people who signed in saw blank user profiles.
The API kept running for about 30 minutes against the almost empty database. For each user who signed in during that time, travis-ci.com created a new user record, and because PostgreSQL does not reset id sequences on truncate, the new records took primary keys from the existing sequence. Users synced from GitHub got new records too, so both new and existing users were affected.
All applications were then taken offline while a replacement database was provisioned from a snapshot taken that morning. After the restore, people who had signed in during those 30 minutes found themselves logged in as the wrong users: the signed token in localStorage that served as their login credential matched user records created after the system restore. Scheduled cron jobs also failed to trigger, because the cron scheduler was not restarted after the restore and the alerts that would have shown this had not been turned back on.
Symptom Data lossService outage
Root cause
Root cause Operator error
- Trigger
- At 12:04 UTC a developer ran a test suite in an old tmux shell that still had a DATABASEURL environment variable pointing at the production database, and the Database Cleaner gem sent a truncate query to it. The query was blocked for about 10 minutes and executed at 12:14 UTC.
Detection
Alerts fired immediately after the truncation, and customers reported missing account data.
Remediation
- Took all applications offline
- Provisioned a replacement database from a snapshot taken that morning
- Reattached the restored database and brought platform services back up
- Worked through the backlog of builds until it cleared
- Revoked all affected login tokens by 14:22 UTC on 14 March
- Contacted affected customers and advised them to rotate stored credentials and check build logs
Action items
- Revoke the truncate permission on the databases
- Make the internal spechelpers check for the DATABASEURL environment variable
- Make the developer shell prompt warn when a DATABASE_URL is set
- Submit a pull request to the Database Cleaner gem guarding against a remote DATABASE_URL
- Create an alias for the follower database so it is easy to connect to for work against production data
- Automate database failover and maintenance to cut manual recovery steps
Lessons
Developers connected to the production primary with write access because the tooling made the read-only follower hard to reach. While responding, Travis CI left user-facing applications running, which created duplicate user ids, and did not turn some alerts back on, which hid the stopped cron scheduler. Even the most experienced developers can make an inadvertent mistake that causes a large outage. The whole production database was recovered with only about 15 minutes of data loss.
Sources
- https://www.traviscistatus.com/incidents/z2b3lz2kwcfp · official-status · archived copy
Related cases
IL-0577Ficilcom's production Neon PostgreSQL database was reset by prisma migrate reset meant for the local databaseFicilcom · 2026same root cause · shared Database
IL-0576Every dataset in CareNet's production BigQuery data warehouse was deleted, and UNDROP SCHEMA could not restore themCareNet · 2025same root cause · shared Database
IL-0242Both PostgreSQL servers behind matrix.org were lost during a disk expansion, and the homeserver was down for 24 hoursMatrix.org Foundation · 2025same root cause · shared Database
IL-0582Jobs piled up in Kanmu's PostgreSQL-based qg job queue on Aurora as dequeue slowed and dead tuples kept growingKanmu · 2024same root cause · shared Database
IL-0556A transaction left open during database maintenance stopped uploads and then downloads on Storj's US1 satelliteStorj · 2024same root cause · shared Database
IL-0531Azure Database for MySQL Single Server and MariaDB in West US rejected every new connection for a batch of serversMicrosoft Azure · 2024same root cause · shared Database
IL-0565Cookie Run: Kingdom's CockroachDB cluster failed when 16 of 24 nodes lost their filesystems, causing a 36-hour outageDevsisters · 2022same root cause · shared Database
IL-0207A routine task run from an engineer's local environment switched to production and deleted GitLab.com's Consul release, so Patroni lost Consul and GitLab.com returned 5xx errors to all trafficGitLab · 2022same root cause · shared Database
IL-0431During Mackerel's move to AWS, PostgreSQL failed over to a replica that had stopped replicating, losing an hour of dataHatena · 2017same root cause · shared Database
IL-0506UC Berkeley DataHub logins failed and students got empty home directories after the JupyterHub database was overwrittenUC Berkeley · 2017same root cause · shared Database
IL-0218GitLab.com's primary PostgreSQL data was wiped by mistake and, with no working backup, the site was down about 18 hoursGitLab · 2017same root cause · shared Database
IL-0573A developer's mistake dropped nine main tables of the Baemin billing MariaDB database and payments briefly stoppedWoowa Brothers · 2016same root cause · shared Database
IL-0319Dropping databases on the MySQL replica mysql2 also dropped them on mysql1, as the pair actually ran master-masterOregon State University Open Source Lab · 2015same root cause · shared Database
IL-0337EBS volumes in one US East Availability Zone got stuck after a network change cut EBS nodes off from their replicasAmazon Web Services · 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-0332
- The cases most like it: https://incidentlib-api.benchlet.com/cases/IL-0332/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.