IL-0332 Travis CI · 2018-03-13database
운영 데이터베이스의 모든 테이블이 truncate 돼 travis-ci.com 이 약 5.5시간 동안 멈췄다
2018년 3월 13일 12:04 UTC 에 travis-ci.com 의 운영 데이터베이스에 모든 테이블을 truncate 하는 쿼리가 실수로 실행됐다. 쿼리는 약 10분 동안 막혀 있다가 12:14 UTC 에 실행됐다. 원인을 찾는 데는 하루가 걸렸다. API 로그와 업스트림 제공자가 알려 준 쿼리 출발지 IP 주소로 추적해 보니, Database Cleaner gem 을 쓰는 테스트가 truncate 쿼리를 실행했다. 개발자가 여러 날 전 운영 데이터를 살펴볼 때 쓰던 tmux 세션의 오래된 터미널 창으로 돌아와 테스트 묶음을 돌렸다. 그 셸에는 운영 데이터베이스를 가리키는 DATABASEURL 환경 변수가 남아 있었다. 읽기 전용 팔로워에 접속하기가 도구와 절차 때문에 번거로워서, 쓰기 권한이 있는 주 데이터베이스에 바로 접속하는 일이 흔했다.
알림에 대응하는 동안 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분 분량이었다.
복구 뒤, 30분 동안 로그인했던 사용자들은 다른 사용자로 로그인돼 있었다. localStorage 의 서명된 토큰이 복구 뒤 새로 만든 사용자 레코드와 맞물렸기 때문이다. GitHub 에서 사용자를 주기적으로 동기화하므로 새 사용자와 기존 사용자가 모두 영향을 받았다. 영향받은 토큰은 3월 14일 14:22 UTC 까지 모두 폐기했다. 접근 로그로 살펴보니 일부 고객은 빌드 로그가 노출됐을 수 있었다. 또 다른 일부 고객의 경우, 노출 기간에 그 데이터에 접근할 수 있는 사용자가 적어도 한 명 로그인해 있었다. 팀은 영향받은 고객에게 연락해 자격 증명을 교체하라고 권했다. 복구 뒤 cron 스케줄러를 다시 시작하지 않아 예약된 cron 작업이 실행되지 않았다. 일부 알림도 다시 켜지 않아 팀은 이를 알지 못했다.
- 심각도
- –
- 영향 범위
- 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
- 영향 사용자
- New and existing travis-ci.com users; those who signed in during the 30 minutes after the truncation were logged in as wrong users
- 지역
- –
- 감지까지
- –
- 복구까지
- 5h 30m
증상
2018년 3월 13일 12:14 UTC 에 travis-ci.com 의 운영 데이터베이스에서 모든 테이블이 비워졌고, 곧바로 알림이 울렸다. 12:34 UTC 에 팀은 travis-ci.com 의 대규모 장애를 공지했다. 12:45 UTC 에는 고객들이 계정 데이터가 없어졌다고 신고했고, 팀도 같은 증상을 확인했다.
데이터베이스가 거의 빈 채로 API 는 약 30분 동안 계속 동작했다. 그동안 travis-ci.com 에 로그인한 사람은 빈 사용자 프로필을 봤다. 옛 사용자 레코드가 지워졌기 때문에 시스템은 이들의 레코드를 새로 만들었다. PostgreSQL 은 truncate 때 id 시퀀스를 되돌리지 않으므로 새 레코드는 기존 시퀀스에서 기본 키를 받았다. GitHub 에서 사용자를 주기적으로 동기화하기 때문에 새 사용자뿐 아니라 기존 사용자도 새 레코드를 받았다.
시스템이 복구된 뒤, 데이터베이스가 비워진 때부터 애플리케이션을 내리기까지 30분 동안 로그인했던 사람들은 다른 사용자로 로그인돼 있었다. localStorage 에 있는 서명된 토큰이 복구 뒤에 새로 만들어진 사용자 레코드와 맞물렸기 때문이다.
복구 뒤에는 cron 스케줄러를 다시 시작하지 않아 예약된 cron 작업이 실행되지 않는 오류가 났다. 일부 알림을 다시 켜지 않아 팀은 cron 스케줄러가 멈춘 것을 알지 못했다.
시스템이 돌아온 뒤에도 빌드 적체가 3.5시간 더 이어졌다. Mac 인프라와 `sudo: required` 인 GCE 인프라에 적체가 남았고, `sudo:false` 인 EC2 인프라는 적체가 없었다.
원인
원인 운영 실수
- 촉발
- 2018년 3월 13일 12:04 UTC 에 개발자가 tmux 세션의 오래된 터미널 창에서 테스트 묶음을 돌렸다. 이 셸에는 여러 날 전 운영 데이터를 살펴볼 때 설정한 DATABASEURL 환경 변수가 운영 데이터베이스를 가리킨 채 남아 있었다. 그래서 Database Cleaner gem 이 운영 데이터베이스에 truncate 쿼리를 실행했다.
감지
데이터베이스가 비워진 직후 알림이 울렸다. 12:45 UTC 에는 고객들도 계정 데이터가 없어졌다고 신고했다.
조치
- 시스템의 모든 애플리케이션을 내렸다.
- 그날 아침에 찍은 스냅샷에서 대체 데이터베이스를 만들었다.
- 17:06 UTC 에 데이터베이스를 플랫폼에 다시 연결해 www.travis-ci.com 과 api 를 올렸다.
- 빌드 적체를 처리했고, 20:59 UTC 에 적체가 모두 풀려 시스템이 정상으로 돌아왔다.
- 다른 사용자로 로그인되는 문제를 막으려고 3월 14일 수요일 14:22 UTC 까지 영향받은 토큰을 모두 폐기했다.
- 영향받은 고객에게 travis-ci.com 에 저장한 자격 증명을 암호화 여부와 상관없이 교체하고, 빌드 로그에 민감한 정보가 없는지 확인하라고 권했다.
후속 조치
- 데이터베이스에서 truncate 권한을 거둬 테이블을 truncate 할 수 없게 했다.
- 내부 spechelpers 가 DATABASEURL 환경 변수를 검사하도록 고쳤다.
- 개발 도구에서 DATABASE_URL 이 설정돼 있으면 셸 프롬프트가 경고하게 했다.
- 원격 DATABASE_URL 을 실수로 쓰는 일을 막도록 Database Cleaner gem 에 Pull Request 를 보냈다.
- 운영 데이터로 시험해야 할 때 팔로워 데이터베이스를 쉽게 찾아 접속하도록 별칭을 만들었다.
- 이런 상황에서 복구에 드는 시간과 수동 단계를 줄이려고 데이터베이스 장애 조치와 유지 보수를 자동화했다.
교훈
원인을 이해하고 서둘러 바로잡는 동안 사용자용 애플리케이션을 켜 둔 것이 실수였다. 이 때문에 사용자 id 가 겹쳐 만들어졌다. 일부 알림을 다시 켜지 않아 팀은 cron 스케줄러가 멈춘 것을 몰랐다. 도구와 절차 때문에 읽기 전용 팔로워에 접속하기가 어려웠고, 그래서 운영 데이터를 살펴볼 때 쓰기 권한이 있는 주 데이터베이스에 바로 접속하는 지름길이 흔해졌다. 가장 경험 많은 개발자도 실수로 큰 장애를 낼 수 있다는 점을 다시 확인했다. 대응하면서 놓친 단계가 있었고, 그 단계를 밟았다면 문제가 줄었을 것이다.
출처
- https://www.traviscistatus.com/incidents/z2b3lz2kwcfp · official-status · 보관본
관련 사례
같은 원인 · 공통 데이터베이스
같은 원인 · 공통 데이터베이스
IL-0242primary 와 secondary PostgreSQL 데이터베이스를 모두 잃어 matrix.org homeserver 가 약 24시간 동안 멈췄다Matrix.org Foundation · 2025같은 원인 · 공통 데이터베이스
같은 원인 · 공통 데이터베이스
같은 원인 · 공통 데이터베이스
IL-0531West US 의 Azure Database for MySQL Single Server 와 MariaDB 일부 서버로 가는 새 연결이 모두 거부됐다Microsoft Azure · 2024같은 원인 · 공통 데이터베이스
IL-0565쿠키런: 킹덤의 CockroachDB 클러스터가 노드 24대 중 16대의 파일 시스템을 잃고 멈춰 게임이 36시간 넘게 점검에 들어갔다Devsisters · 2022같은 원인 · 공통 데이터베이스
IL-0207로컬 개발 환경에서 돌린 작업이 운영 컨텍스트로 넘어가 Kubernetes 클러스터의 Consul 릴리스를 통째로 지웠고, Patroni 가 무너져 GitLab.com 전체가 5XX 오류를 냈다.GitLab · 2022같은 원인 · 공통 데이터베이스
같은 원인 · 공통 데이터베이스
IL-0506UC Berkeley DataHub 의 JupyterHub sqlite 데이터베이스가 빈 데이터베이스로 덮어써져 로그인이 실패하고 홈 디렉터리가 비어 보이고 다른 사용자로 로그인되는 문제가 났다UC Berkeley · 2017같은 원인 · 공통 데이터베이스
IL-0218복제를 다시 맞추다 PostgreSQL 프라이머리의 데이터 디렉터리가 지워지고 백업이 작동하지 않아 GitLab.com 이 약 18시간 멈추고 데이터를 잃었다GitLab · 2017같은 원인 · 공통 데이터베이스
같은 원인 · 공통 데이터베이스
IL-0319mysql2 를 다시 적재하려고 데이터베이스를 지우는 동안 mysql1 의 데이터베이스도 함께 지워졌다Oregon State University Open Source Lab · 2015같은 원인 · 공통 데이터베이스
같은 원인 · 공통 데이터베이스
내 클러스터에도 있는지 확인하기
AI 도구와 스크립트용
- 이 사례의 JSON: https://incidentlib-api.benchlet.com/cases/IL-0332
- 가장 비슷한 사례들: https://incidentlib-api.benchlet.com/cases/IL-0332/similar
- MCP 서버(Streamable HTTP, 키 없음):
https://incidentlib-mcp.benchlet.com/mcp
API 는 키가 필요 없습니다. MCP 서버는 search_incidents, get_incident, list_incidents, get_topic, vocab 도구를 내놓습니다. 사용법은 API 와 MCP.