Incident Library

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 에는 고객들도 계정 데이터가 없어졌다고 신고했다.

조치

후속 조치

교훈

원인을 이해하고 서둘러 바로잡는 동안 사용자용 애플리케이션을 켜 둔 것이 실수였다. 이 때문에 사용자 id 가 겹쳐 만들어졌다. 일부 알림을 다시 켜지 않아 팀은 cron 스케줄러가 멈춘 것을 몰랐다. 도구와 절차 때문에 읽기 전용 팔로워에 접속하기가 어려웠고, 그래서 운영 데이터를 살펴볼 때 쓰기 권한이 있는 주 데이터베이스에 바로 접속하는 지름길이 흔해졌다. 가장 경험 많은 개발자도 실수로 큰 장애를 낼 수 있다는 점을 다시 확인했다. 대응하면서 놓친 단계가 있었고, 그 단계를 밟았다면 문제가 줄었을 것이다.

출처

관련 사례

내 클러스터에도 있는지 확인하기

kubegap 은 Claude Code 나 Codex 에서 쿠버네티스 클러스터를 읽기만 하며 이 사례와 같은 상태를 점검합니다. 이 사례의 구성 요소: 데이터베이스. 점검 항목의 근거는 이 사례 같은 공개 보고서입니다.

AI 도구와 스크립트용

API 는 키가 필요 없습니다. MCP 서버는 search_incidents, get_incident, list_incidents, get_topic, vocab 도구를 내놓습니다. 사용법은 API 와 MCP.