IL-0218 GitLab · 2017-01-31databaseazure
복제를 다시 맞추다 PostgreSQL 프라이머리의 데이터 디렉터리가 지워지고 백업이 작동하지 않아 GitLab.com 이 약 18시간 멈추고 데이터를 잃었다
GitLab.com 은 프라이머리 db1.cluster.gitlab.com 과 핫 스탠바이 세컨더리 db2.cluster.gitlab.com 으로 PostgreSQL 을 운영했고, 세컨더리는 페일오버에만 썼다. 2017년 1월 31일 한 엔지니어가 pgpool-II 를 시험하려고 17:20 UTC 무렵 운영 데이터베이스의 LVM 스냅샷을 떠 스테이징에 올렸다. 19:00 UTC 무렵 스팸으로 의심되는 부하가 올랐고, 어뷰징 신고 때문에 실수로 삭제 예약된 GitLab 직원 계정과 데이터를 지우는 백그라운드 작업도 부하를 보탰다. 23:00 UTC 무렵 세컨더리의 복제가 뒤처지다 멈췄는데, 필요한 WAL 세그먼트를 프라이머리가 이미 지웠고 WAL 아카이빙이 없어 세컨더리를 손으로 다시 맞춰야 했다.
엔지니어는 세컨더리 데이터 디렉터리를 지우고 pg_basebackup 을 돌렸지만 출력 없이 멈췄다. max_wal_senders 를 3 에서 32 로 올렸다가 세마포어 문제로 PostgreSQL 이 재시작을 거부하자 max_connections 를 8000 에서 2000 으로 낮췄다. 그래도 pg_basebackup 이 바로 시작하지 않자 23:30 UTC 무렵 엔지니어는 데이터 디렉터리를 다시 지웠는데, 그곳은 세컨더리가 아니라 프라이머리였다. 1~2초 만에 멈췄지만 약 300 GB 가 지워졌다. 백업을 찾아보니 pg_dump 백업은 9.6 데이터베이스를 pg_dump 9.2 로 뜨다 오류로 끝나 S3 버킷이 비어 있었다. 백업 작업이 데이터 디렉터리가 없는 애플리케이션 서버에서 돌았기 때문에 Omnibus 가 9.2 를 골랐다. 실패 알림 메일은 DMARC 서명이 없어 받는 서버가 거부했다. 데이터베이스 서버에는 Azure 디스크 스냅샷도 켜져 있지 않았다.
GitLab 은 데이터 손실을 줄이려고 6시간 전에 손으로 뜬 LVM 스냅샷을 쓰기로 했다. 스테이징의 PostgreSQL 데이터 디렉터리를 운영으로 복사하는 데 약 60Mbps 로 제한된 느린 디스크 때문에 약 18시간이 걸렸다. 스테이징 복사본에서는 웹훅을 지워 두었으므로 웹훅은 따로 띄운 데이터베이스에서 SQL 덤프로 가져왔고, 이전 ID 를 다시 쓰지 않게 시퀀스를 100,000 씩 올렸다. 2월 1일 17:00 UTC 에 웹훅 없이 데이터베이스를 복원했고, 18:00 UTC 무렵 웹훅 복원과 확인을 마쳤다. 1월 31일 17:20 UTC 이후의 데이터베이스 변경은 잃었다. GitLab 은 이 구간의 끝을 글 첫머리에서는 00:00 UTC 로, 데이터 손실 절에서는 23:30 UTC 로 적었고, 프로젝트 약 5,000개, 댓글 5,000개, 새 사용자 계정 약 700개가 영향을 받은 것으로 추정했다. Git 저장소와 위키는 따로 저장돼 지워지지 않았다. GitLab 은 복구 과정을 공개 Google 문서와 YouTube 생중계, Twitter 로 알렸다.
- 심각도
- –
- 영향 범위
- GitLab.com unavailable for about 18 hours; database changes made after 17:20 UTC on 31 January, such as projects, comments, user accounts, issues and snippets, were lost (GitLab gives the end of the window as 00:00 UTC in its summary and 23:30 UTC in its data loss section); Git repositories and wikis were unavailable but not lost
- 영향 사용자
- GitLab.com users; roughly 5,000 projects, 5,000 comments and 700 new user accounts lost; GitLab Enterprise, GitHost and self-managed GitLab CE users not affected
- 지역
- –
- 감지까지
- –
- 복구까지
- 18h
증상
2017년 1월 31일 19:00 UTC 무렵 GitLab.com 의 데이터베이스 부하가 올랐고, 많은 사용자가 이슈와 머지 리퀘스트에 댓글을 달지 못했다. 스팸으로 의심되는 부하였고, 잡는 데 몇 시간이 걸렸다.
23:00 UTC 무렵 부하 때문에 PostgreSQL 세컨더리의 복제가 뒤처지다가 멈췄다. 세컨더리에 필요한 WAL 세그먼트를 프라이머리가 이미 지운 뒤였고, WAL 아카이빙을 쓰지 않았으므로 세컨더리를 손으로 다시 동기화해야 했다.
엔지니어가 세컨더리의 데이터 디렉터리를 지우고 pg_basebackup 을 돌렸지만, --verbose 옵션을 켰는데도 아무 의미 있는 출력 없이 멈춰 있었다. 몇 번 다시 시도하자 pg_basebackup 은 max_wal_senders 로 정하는 복제 연결이 master 에 모자라 접속할 수 없다고 알렸다.
max_wal_senders 를 3 에서 32 로 올리자 PostgreSQL 은 세마포어를 너무 많이 만든다며 재시작을 거부했다. max_connections 가 8000 이었고, 2000 으로 낮추자 재시작됐다. 그래도 pg_basebackup 은 바로 복제를 시작하지 않았고, strace 로 보니 poll 호출에서 멈춰 있었을 뿐 다른 단서는 없었다.
23:30 UTC 무렵 엔지니어는 앞선 시도에서 pg_basebackup 이 세컨더리 데이터 디렉터리에 파일을 남겼다고 생각해 디렉터리를 다시 지웠는데, 실제로는 프라이머리에서 지웠다. 1~2초 만에 멈췄지만 약 300 GB 가 이미 지워졌다. pg_basebackup 은 프라이머리가 복제 데이터를 보낼 때까지 조용히 기다리는 것이 정상 동작이었지만, 이 동작은 런북에도 공식 문서에도 분명히 적혀 있지 않았다.
복원하려고 백업을 찾자 pg_dump 백업을 올리는 S3 버킷이 비어 있었고 최근 백업은 어디에도 없었다. 데이터베이스 서버에는 Azure 디스크 스냅샷이 켜져 있지 않았고, 프라이머리와 세컨더리 모두 데이터가 지워져 어느 쪽에서도 복원할 수 없었다. pg_dump 백업은 백업 작업이 PostgreSQL 9.6 에 pg_dump 9.2 를 돌려 오류로 끝나고 있었다. cron 실패 알림 메일은 DMARC 서명이 없어 받는 메일 서버가 거부했다. 쓸 수 있는 사본은 스테이징용 LVM 스냅샷뿐이었다. 하나는 거의 24시간 전 것이었고 하나는 장애 약 6시간 전에 엔지니어가 손으로 뜬 것이었다. 약 60Mbps 로 제한된 스테이징 디스크에서 복사하는 데 약 18시간이 걸렸다.
GitLab.com 은 1월 31일 17:20 UTC 이후의 프로젝트, 댓글, 사용자 계정, 이슈, 스니펫 같은 데이터베이스 변경을 잃었다. GitLab 은 이 구간의 끝을 글 첫머리에서는 00:00 UTC 로, 데이터 손실 절에서는 23:30 UTC 로 적었다. 프로젝트 약 5,000개, 댓글 5,000개, 새 사용자 계정 약 700개가 영향을 받은 것으로 추정했다. Git 저장소와 위키는 장애 중 쓸 수 없었지만 지워지지 않았다.
공개 모니터링 사이트 dashboards.gitlab.com 은 장애 중 몰린 사용자 부하를 견디지 못했다.
원인
원인 운영 실수
- 촉발
- 1월 31일 23:00 UTC 무렵 데이터베이스 부하로 세컨더리의 복제가 멈췄고, 세컨더리를 다시 맞추던 엔지니어가 세컨더리가 아니라 프라이머리에서 PostgreSQL 데이터 디렉터리를 지웠다.
감지
프라이머리에서 데이터 디렉터리를 지운 엔지니어가 스스로 실수를 알아채고 1~2초 만에 삭제를 멈췄다.
조치
- pg_basebackup 이 복제 연결이 모자라다고 알리자 max_wal_senders 를 기본값 3 에서 32 로 잠시 올렸고, PostgreSQL 이 재시작을 거부하자 max_connections 를 8000 에서 2000 으로 낮췄다.
- 엔지니어가 프라이머리에서 데이터를 지우고 있다는 것을 알아채고 1~2초 만에 삭제 작업을 멈췄지만, 약 300 GB 가 이미 지워졌다.
- 데이터 손실을 가장 줄일 수 있는 유일한 방법으로 장애 6시간 전에 만든 LVM 스냅샷을 쓰기로 하고, 스테이징의 PostgreSQL 데이터 디렉터리를 약 18시간 동안 운영으로 복사해 복원했다.
- 웹훅 삭제를 거치지 않은 별도 스테이징 데이터베이스를 LVM 스냅샷으로 띄워 웹훅 테이블의 SQL 덤프를 만들고, 복원한 GitLab.com 데이터베이스에 가져왔다.
- 장애 전에 쓰였을 수 있는 ID 를 다시 쓰지 않도록 모든 데이터베이스 시퀀스를 100,000 씩 올렸다.
- GitLab.com 을 차례로 다시 열었다.
- 17:20 UTC 이후에 만든 프로젝트는 NFS 클러스터에 남은 Git 저장소로 가장 기본적인 형태만 복원했고, 이슈와 머지 리퀘스트 같은 데이터는 되살리지 못했다.
후속 조치
- 모든 호스트의 셸 프롬프트인 PS1 을 고쳐 호스트와 환경을 더 분명히 구분한다.
- 백업을 Prometheus 로 모니터링한다.
- PostgreSQL 의 max_connections 를 알맞은 값으로 둔다.
- PostgreSQL 의 시점 복구와 연속 아카이빙을 검토한다.
- 운영 데이터베이스의 LVM 스냅샷을 매시간 뜬다.
- 운영 데이터베이스에 Azure 디스크 스냅샷을 켠다.
- 스테이징을 ARM 환경으로 옮긴다.
- 운영 복제본을 되살린다.
- PostgreSQL 백업 복원을 자동으로 시험한다.
- PostgreSQL 복제 문서와 런북을 고친다.
- PostgreSQL 백업에 pgbarman 을 쓸지 검토한다.
- 데이터베이스 백업과 실시간 복제에 WAL-E 를 쓸지 검토한다.
- 스트리밍 방식의 데이터베이스 복원을 만든다.
- 데이터 내구성을 책임질 담당자를 정한다.
- 세컨더리를 여러 대 두고 부하를 나누도록 pgpool-II 3.6.1 을 묶고 PostgreSQL 연결 풀링과 부하 분산을 구성한다.
- 개발자가 데이터베이스 마이그레이션을 시험할 수 있도록 스테이징에서 Rails 마이그레이션을 실행하고 되돌리는 도구를 만든다.
- 데이터베이스뿐 아니라 GitLab.com 인프라 전체의 복구 절차를 만들고 그 절차의 담당자를 정한다.
- 공개 백업 모니터링 대시보드를 만들었고, 지금은 pg_dump 백업 데이터만 있지만 데이터를 더 넣는다.
- 어뷰징 신고와 대응 시스템을 고쳐 스팸 신고로 사용자를 지울 때 데이터를 바로 영구 삭제하지 않게 한다.
- 앞으로 공개하는 장애 문서에서는 엔지니어의 이름을 가린다.
교훈
GitLab 은 장애를 GitLab.com 이 약 18시간 멈춘 문제와 복원이 18시간 넘게 걸린 문제로 나눠 5 Whys 로 분석했다. 세컨더리를 다시 만드는 작업은 자동화되지도 제대로 문서화되지도 않아 손으로 해야 했다. 표준 백업은 PostgreSQL 버전이 맞지 않아 조용히 실패했고, 실패 알림 메일은 DMARC 서명이 없어 거부됐다. 다른 백업 절차를 믿고 Azure 디스크 스냅샷을 켜지 않았다. 백업 절차를 맡은 사람이 없어 아무도 정기적으로 시험하지 않았다.
GitLab 은 운영 엔지니어가 특정 명령을 못 쓰게 막는 것보다 재해 복구를 고치고 지금 어느 호스트를 쓰는지 더 분명히 보이게 하는 데 집중하기로 했다. rm 을 더 안전한 별칭으로 바꾸면 실수로 지우는 것은 막지만 디스크 손상처럼 데이터를 잃는 다른 경우는 막지 못한다. 이상적인 환경은 실수해도 영향이 거의 없이 빠르게 복구할 수 있는 곳이고, 그러려면 복구 절차를 정기적으로 해 보고 변경을 쉽게 시험하고 되돌릴 수 있어야 한다.
출처
관련 사례
IL-0242primary 와 secondary PostgreSQL 데이터베이스를 모두 잃어 matrix.org homeserver 가 약 24시간 동안 멈췄다Matrix.org Foundation · 2025같은 원인 · 공통 데이터베이스, 데이터베이스 백업과 스냅숏, 데이터베이스 복제
IL-0319mysql2 를 다시 적재하려고 데이터베이스를 지우는 동안 mysql1 의 데이터베이스도 함께 지워졌다Oregon State University Open Source Lab · 2015같은 원인 · 공통 데이터베이스, 데이터베이스 백업과 스냅숏, 데이터베이스 복제
같은 원인 · 공통 데이터베이스, 데이터베이스 복제
같은 원인 · 공통 데이터베이스, 데이터베이스 백업과 스냅숏
같은 원인 · 공통 데이터베이스 백업과 스냅숏, 클라우드 블록 스토리지
같은 원인 · 공통 데이터베이스, 클라우드 블록 스토리지
같은 원인 · 공통 데이터베이스
같은 원인 · 공통 데이터베이스
같은 원인 · 공통 데이터베이스
같은 원인 · 공통 데이터베이스
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-0534US-EAST-1 의 Amazon S3 가 몇 시간 동안 요청을 처리하지 못했고 S3 에 기대는 AWS 서비스도 함께 멈췄다Amazon Web Services · 2017같은 원인 · 공통 클라우드 블록 스토리지
IL-0506UC Berkeley DataHub 의 JupyterHub sqlite 데이터베이스가 빈 데이터베이스로 덮어써져 로그인이 실패하고 홈 디렉터리가 비어 보이고 다른 사용자로 로그인되는 문제가 났다UC Berkeley · 2017같은 원인 · 공통 데이터베이스
내 클러스터에도 있는지 확인하기
AI 도구와 스크립트용
- 이 사례의 JSON: https://incidentlib-api.benchlet.com/cases/IL-0218
- 가장 비슷한 사례들: https://incidentlib-api.benchlet.com/cases/IL-0218/similar
- MCP 서버(Streamable HTTP, 키 없음):
https://incidentlib-mcp.benchlet.com/mcp
API 는 키가 필요 없습니다. MCP 서버는 search_incidents, get_incident, list_incidents, get_topic, vocab 도구를 내놓습니다. 사용법은 API 와 MCP.