{"id":"IL-0332","org":"Travis CI","date":"2018-03-13","docs":[],"title":{"en":"A test run against the production database truncated every table and travis-ci.com was down for about 5.5 hours","ko":"운영 데이터베이스의 모든 테이블이 truncate 돼 travis-ci.com 이 약 5.5시간 동안 멈췄다"},"domain":["database"],"impact":{"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":"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":"unknown"},"review":{"written_by":"session","reviewed_at":null,"reviewed_by":null},"lessons":{"en":"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.","ko":"원인을 이해하고 서둘러 바로잡는 동안 사용자용 애플리케이션을 켜 둔 것이 실수였다. 이 때문에 사용자 id 가 겹쳐 만들어졌다. 일부 알림을 다시 켜지 않아 팀은 cron 스케줄러가 멈춘 것을 몰랐다. 도구와 절차 때문에 읽기 전용 팔로워에 접속하기가 어려웠고, 그래서 운영 데이터를 살펴볼 때 쓰기 권한이 있는 주 데이터베이스에 바로 접속하는 지름길이 흔해졌다. 가장 경험 많은 개발자도 실수로 큰 장애를 낼 수 있다는 점을 다시 확인했다. 대응하면서 놓친 단계가 있었고, 그 단계를 밟았다면 문제가 줄었을 것이다."},"related":[],"sources":[{"url":"https://www.traviscistatus.com/incidents/z2b3lz2kwcfp","kind":"official-status","archived":"https://web.archive.org/web/20261101131017/https://www.traviscistatus.com/incidents/z2b3lz2kwcfp"}],"summary":{"en":"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.\n\nResponders 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.","ko":"2018년 3월 13일 12:04 UTC 에 travis-ci.com 의 운영 데이터베이스에 모든 테이블을 truncate 하는 쿼리가 실수로 실행됐다. 쿼리는 약 10분 동안 막혀 있다가 12:14 UTC 에 실행됐다. 원인을 찾는 데는 하루가 걸렸다. API 로그와 업스트림 제공자가 알려 준 쿼리 출발지 IP 주소로 추적해 보니, Database Cleaner gem 을 쓰는 테스트가 truncate 쿼리를 실행했다. 개발자가 여러 날 전 운영 데이터를 살펴볼 때 쓰던 tmux 세션의 오래된 터미널 창으로 돌아와 테스트 묶음을 돌렸다. 그 셸에는 운영 데이터베이스를 가리키는 DATABASEURL 환경 변수가 남아 있었다. 읽기 전용 팔로워에 접속하기가 도구와 절차 때문에 번거로워서, 쓰기 권한이 있는 주 데이터베이스에 바로 접속하는 일이 흔했다.\n\n알림에 대응하는 동안 API 는 거의 빈 데이터베이스에 연결된 채 약 30분 동안 계속 동작했다. 그 사이 로그인한 사용자는 빈 프로필을 봤다. 시스템은 이들의 사용자 레코드를 새로 만들었다. 팀은 결국 모든 애플리케이션을 내렸고, 그날 아침에 찍은 스냅샷에서 대체 데이터베이스를 만들었다. 17:06 UTC 에 데이터베이스를 플랫폼에 다시 연결해 www.travis-ci.com 과 api 가 올라왔다. 17:13 UTC 에는 모든 플랫폼 서비스가 돌아갔다. travis-ci.com 은 12:14 UTC 부터 약 5.5시간 동안 동작하지 않았다. 빌드 적체는 3.5시간 더 이어져 20:59 UTC 에 풀렸다. 데이터 손실은 약 15분 분량이었다.\n\n복구 뒤, 30분 동안 로그인했던 사용자들은 다른 사용자로 로그인돼 있었다. localStorage 의 서명된 토큰이 복구 뒤 새로 만든 사용자 레코드와 맞물렸기 때문이다. GitHub 에서 사용자를 주기적으로 동기화하므로 새 사용자와 기존 사용자가 모두 영향을 받았다. 영향받은 토큰은 3월 14일 14:22 UTC 까지 모두 폐기했다. 접근 로그로 살펴보니 일부 고객은 빌드 로그가 노출됐을 수 있었다. 또 다른 일부 고객의 경우, 노출 기간에 그 데이터에 접근할 수 있는 사용자가 적어도 한 명 로그인해 있었다. 팀은 영향받은 고객에게 연락해 자격 증명을 교체하라고 권했다. 복구 뒤 cron 스케줄러를 다시 시작하지 않아 예약된 cron 작업이 실행되지 않았다. 일부 알림도 다시 켜지 않아 팀은 이를 알지 못했다."},"trigger":{"en":"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.","ko":"2018년 3월 13일 12:04 UTC 에 개발자가 tmux 세션의 오래된 터미널 창에서 테스트 묶음을 돌렸다. 이 셸에는 여러 날 전 운영 데이터를 살펴볼 때 설정한 DATABASEURL 환경 변수가 운영 데이터베이스를 가리킨 채 남아 있었다. 그래서 Database Cleaner gem 이 운영 데이터베이스에 truncate 쿼리를 실행했다."},"severity":"unknown","symptoms":["data-loss","service-unavailable"],"timeline":{"detected_after":null,"resolved_after":"5h 30m"},"date_kind":"incident","detection":{"en":"Alerts fired immediately after the truncation, and customers reported missing account data.","ko":"데이터베이스가 비워진 직후 알림이 울렸다. 12:45 UTC 에는 고객들도 계정 데이터가 없어졌다고 신고했다."},"components":["database"],"root_cause":"human-operation","remediation":[{"text":{"en":"Took all applications offline","ko":"시스템의 모든 애플리케이션을 내렸다."}},{"text":{"en":"Provisioned a replacement database from a snapshot taken that morning","ko":"그날 아침에 찍은 스냅샷에서 대체 데이터베이스를 만들었다."}},{"text":{"en":"Reattached the restored database and brought platform services back up","ko":"17:06 UTC 에 데이터베이스를 플랫폼에 다시 연결해 www.travis-ci.com 과 api 를 올렸다."}},{"text":{"en":"Worked through the backlog of builds until it cleared","ko":"빌드 적체를 처리했고, 20:59 UTC 에 적체가 모두 풀려 시스템이 정상으로 돌아왔다."}},{"text":{"en":"Revoked all affected login tokens by 14:22 UTC on 14 March","ko":"다른 사용자로 로그인되는 문제를 막으려고 3월 14일 수요일 14:22 UTC 까지 영향받은 토큰을 모두 폐기했다."}},{"text":{"en":"Contacted affected customers and advised them to rotate stored credentials and check build logs","ko":"영향받은 고객에게 travis-ci.com 에 저장한 자격 증명을 암호화 여부와 상관없이 교체하고, 빌드 로그에 민감한 정보가 없는지 확인하라고 권했다."}}],"action_items":[{"text":{"en":"Revoke the truncate permission on the databases","ko":"데이터베이스에서 truncate 권한을 거둬 테이블을 truncate 할 수 없게 했다."}},{"text":{"en":"Make the internal spechelpers check for the DATABASEURL environment variable","ko":"내부 spechelpers 가 DATABASEURL 환경 변수를 검사하도록 고쳤다."}},{"text":{"en":"Make the developer shell prompt warn when a DATABASE_URL is set","ko":"개발 도구에서 DATABASE_URL 이 설정돼 있으면 셸 프롬프트가 경고하게 했다."}},{"text":{"en":"Submit a pull request to the Database Cleaner gem guarding against a remote DATABASE_URL","ko":"원격 DATABASE_URL 을 실수로 쓰는 일을 막도록 Database Cleaner gem 에 Pull Request 를 보냈다."}},{"text":{"en":"Create an alias for the follower database so it is easy to connect to for work against production data","ko":"운영 데이터로 시험해야 할 때 팔로워 데이터베이스를 쉽게 찾아 접속하도록 별칭을 만들었다."}},{"text":{"en":"Automate database failover and maintenance to cut manual recovery steps","ko":"이런 상황에서 복구에 드는 시간과 수동 단계를 줄이려고 데이터베이스 장애 조치와 유지 보수를 자동화했다."}}],"contributing":["no-safeguard-on-destructive-change","manual-action-worsened"],"recurrence_of":null,"symptoms_text":{"en":"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.\n\nThe 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.\n\nAll 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.","ko":"2018년 3월 13일 12:14 UTC 에 travis-ci.com 의 운영 데이터베이스에서 모든 테이블이 비워졌고, 곧바로 알림이 울렸다. 12:34 UTC 에 팀은 travis-ci.com 의 대규모 장애를 공지했다. 12:45 UTC 에는 고객들이 계정 데이터가 없어졌다고 신고했고, 팀도 같은 증상을 확인했다.\n\n데이터베이스가 거의 빈 채로 API 는 약 30분 동안 계속 동작했다. 그동안 travis-ci.com 에 로그인한 사람은 빈 사용자 프로필을 봤다. 옛 사용자 레코드가 지워졌기 때문에 시스템은 이들의 레코드를 새로 만들었다. PostgreSQL 은 truncate 때 id 시퀀스를 되돌리지 않으므로 새 레코드는 기존 시퀀스에서 기본 키를 받았다. GitHub 에서 사용자를 주기적으로 동기화하기 때문에 새 사용자뿐 아니라 기존 사용자도 새 레코드를 받았다.\n\n시스템이 복구된 뒤, 데이터베이스가 비워진 때부터 애플리케이션을 내리기까지 30분 동안 로그인했던 사람들은 다른 사용자로 로그인돼 있었다. localStorage 에 있는 서명된 토큰이 복구 뒤에 새로 만들어진 사용자 레코드와 맞물렸기 때문이다.\n\n복구 뒤에는 cron 스케줄러를 다시 시작하지 않아 예약된 cron 작업이 실행되지 않는 오류가 났다. 일부 알림을 다시 켜지 않아 팀은 cron 스케줄러가 멈춘 것을 알지 못했다.\n\n시스템이 돌아온 뒤에도 빌드 적체가 3.5시간 더 이어졌다. Mac 인프라와 `sudo: required` 인 GCE 인프라에 적체가 남았고, `sudo:false` 인 EC2 인프라는 적체가 없었다."},"updated_at":"2026-11-14T12:33:58.501886Z","url":"https://benchlet.com/incidentlib/app/cases/IL-0332","org_slug":"travis-ci"}