{"id":"IL-0210","org":"GitHub","date":"2018-10-21","docs":[],"title":{"en":"GitHub's MySQL clusters failed over to the US West Coast and the site served slow, stale, inconsistent data for 24 hours","ko":"43초 네트워크 단절 뒤 MySQL 프라이머리가 미국 서부로 넘어가 GitHub.com 이 24시간 11분 동안 느려지고 오래된 데이터를 보여 줬다"},"domain":["database","networking"],"impact":{"scope":"GitHub.com degraded for 24 hours and 11 minutes: out-of-date and inconsistent data, slow pages, and no webhook delivery or GitHub Pages builds for most of the incident; a few seconds of database writes needed manual reconciliation","users":"unknown","region":"US East Coast and US West Coast data centers"},"review":{"written_by":"session","reviewed_at":null,"reviewed_by":null},"lessons":{"en":"Orchestrator did what it was configured to do, but the application tier could not work with primaries on the other side of the country: leader election within one region is generally safe, while the sudden cross-country latency was a major contributing factor. The behaviour emerged from a partition larger than any GitHub had seen. Replication caught up along a power decay curve, not a straight line, so the linear estimates of recovery time were wrong. GitHub concluded that tighter operational controls and faster response are not enough on their own and that failure scenarios have to be validated before they happen.","ko":"GitHub 은 Orchestrator 가 설정대로 움직였지만 애플리케이션 계층은 그 토폴로지 변경을 견디지 못했다고 봤다. 리전 안의 리더 선출은 대체로 안전하지만, 갑자기 생긴 대륙 간 지연이 이번 장애를 키운 큰 요인이었다. 자동 페일오버 설정은 애플리케이션이 기대하는 것과 맞춰야 한다.\n\nGitHub 은 이만큼 복잡한 서비스 체계에서는 운영 통제를 조이거나 대응을 빠르게 하는 것만으로는 안정성을 지킬 수 없다고 판단했다. 그래서 가정을 더 적극적으로 시험하고, 장애 시나리오를 사용자에게 닿기 전에 검증하기로 했다. 복구 예상 시간은 모든 변수를 고려하지 못해 틀렸다."},"related":[],"sources":[{"url":"https://github.blog/news-insights/company-news/oct21-post-incident-analysis/","kind":"blog","archived":"https://web.archive.org/web/20261101113342/https://github.blog/news-insights/company-news/oct21-post-incident-analysis/"}],"summary":{"en":"GitHub stores non-Git metadata, such as pull requests, issues, authentication and background job coordination, in several MySQL clusters split by function, from hundreds of gigabytes to nearly five terabytes, each with up to dozens of read replicas. Applications write to each cluster's primary and read mostly from replicas. Orchestrator, which is built on Raft consensus, manages the MySQL topologies and performs automated failover. During the 43-second partition between the US East Coast hub and the primary East Coast data center, the Orchestrator nodes in the US West Coast data center and in the US East Coast public cloud formed a quorum without the primary data center and failed clusters over so that writes went to the West Coast. When connectivity came back, the application tier immediately sent writes to the new West Coast primaries. A short window of East Coast writes had not been replicated, so both sides now held writes the other did not have.\n\nResponders locked the internal deployment tooling, raised the site status to yellow and then red, and paged database engineers. Because the West Coast had taken more than 30 minutes of user writes, GitHub chose to fail forward to protect that data, accepting that the service would be unusable for many users. Webhook delivery and GitHub Pages builds were paused to stop jobs writing more metadata. From 00:41 UTC on 22 October the affected clusters were restored from backups, taken every four hours and kept in public cloud blob storage, into the East Coast data center, where they then replicated from the West Coast; later the teams found ways to restore directly from the West Coast to get around the off-site download limits. By 11:12 UTC all primaries were back in the East Coast, but many read replicas were hours behind, and with the morning load the lag grew. Read replicas that had been provisioned in the US East Coast public cloud spread the read load enough for replication to catch up.\n\nAt 16:24 UTC the replicas were in sync and GitHub failed over to the original topology, keeping the status red while it worked through more than five million queued hook events and 80 thousand Pages builds. About 200,000 webhook payloads had outlived an internal TTL and were dropped, so processing was paused and the TTL raised. At 23:03 UTC all pending webhooks and Pages builds were processed and the status went green, after 24 hours and 11 minutes of degradation. No user data was lost; GitHub captured the MySQL binary logs with the East Coast writes that had not reached the West Coast, for example 954 writes on one of the busiest clusters, and was reconciling them.","ko":"GitHub 은 사용자용 서비스 대부분을 자체 데이터센터에서 운영하고, Git 객체가 아닌 메타데이터를 기능별로 나눈 여러 MySQL 클러스터에 저장한다. 쓰기는 클러스터마다 프라이머리로, 읽기는 대부분 복제본으로 보낸다. 토폴로지 관리와 자동 페일오버는 Raft 합의 위에 만든 Orchestrator 가 맡는다. 2018년 10월 21일 22:52 UTC 에 광 장비 교체 작업으로 동부 네트워크 허브와 동부 주 데이터센터 사이가 43초 동안 끊겼다. 이 사이 동부 주 데이터센터에 있던 Orchestrator 는 리더 자리에서 물러났고, 서부 데이터센터와 동부 퍼블릭 클라우드의 Orchestrator 노드가 쿼럼을 이뤄 클러스터들의 프라이머리를 서부로 넘겼다. 연결이 돌아오자 애플리케이션은 바로 서부 프라이머리에 쓰기 시작했고, 동부에 남은 몇 초 분량의 복제되지 않은 쓰기 때문에 양쪽 데이터가 갈라졌다.\n\n대응 팀은 23:07 UTC 에 배포 도구를 잠갔고, 23:09 UTC 에 상태를 노란색으로, 23:13 UTC 에 빨간색으로 올렸다. 서부에 30분 넘게 쓰인 데이터를 지키려고 동부로 되돌리는 대신 서부의 현재 상태에서 복구를 이어 가기로 했다. 데이터 정합성을 사이트 사용성과 복구 시간보다 앞에 두어 웹훅 전송과 GitHub Pages 빌드를 멈췄다. 계획은 백업에서 복원하고, 두 사이트의 복제본을 맞추고, 안정된 토폴로지로 돌아간 뒤, 쌓인 작업을 처리하는 것이었다. MySQL 백업은 4시간마다 퍼블릭 클라우드 블롭 저장소에 쌓였는데, 수 테라바이트를 내려받고 압축을 풀고 체크섬을 확인해 새 서버에 적재하는 데 몇 시간이 걸렸다. 복원 절차는 매일 시험했지만 클러스터 전체를 백업으로 다시 만든 것은 이번이 처음이었다. 10월 22일 06:51 UTC 에 여러 클러스터가 동부에서 복원을 마쳤고, 팀은 외부 저장소의 처리량 한계를 피하려고 서부에서 직접 복원하는 방법을 찾았다.\n\n11:12 UTC 에 모든 프라이머리가 동부로 돌아와 응답이 크게 빨라졌지만, 수십 대의 읽기 복제본이 몇 시간씩 뒤처져 사용자는 어긋난 데이터를 봤다. 복제 따라잡기는 선형 추정보다 오래 걸렸고, 업무 시간의 쓰기 부하로 지연이 더 늘었다. 앞서 동부 퍼블릭 클라우드에 띄우기 시작한 읽기 복제본이 준비되자 읽기 부하를 나눌 수 있었고 복제가 따라잡았다. 16:24 UTC 에 원래 토폴로지로 페일오버했고, 웹훅 이벤트 500만 건 이상과 Pages 빌드 8만 건의 밀린 작업을 처리했다. 이 과정에서 웹훅 페이로드 약 200,000건이 내부 TTL 을 넘겨 버려져 TTL 을 늘렸다. 23:03 UTC 에 밀린 작업을 모두 처리하고 상태를 녹색으로 돌렸다. 장애는 24시간 11분 이어졌고, 사용자 데이터는 잃지 않았지만 몇 초 분량의 쓰기는 손으로 맞추는 작업이 남았다."},"trigger":{"en":"At 22:52 UTC on 21 October, routine maintenance to replace failing 100G optical equipment cut connectivity between GitHub's US East Coast network hub and its primary US East Coast data center for 43 seconds.","ko":"10월 21일 22:52 UTC 에 고장 난 100G 광 장비를 교체하는 정기 유지보수 작업 중 미국 동부 네트워크 허브와 동부 주 데이터센터 사이의 연결이 끊겼다. 연결은 43초 만에 돌아왔다."},"severity":"unknown","symptoms":["split-brain","failover-failed","replication-lag","high-latency"],"timeline":{"detected_after":"2m","resolved_after":"24h 11m"},"date_kind":"incident","detection":{"en":"GitHub's internal monitoring raised alerts on numerous faults at 22:54 UTC, two minutes after the partition. By 23:02 UTC the first responders had found, by querying the Orchestrator API, that many database clusters had a topology with only US West Coast servers.","ko":"22:54 UTC 에 내부 모니터링 시스템이 알림을 내기 시작해 여러 엔지니어가 알림을 분류했다. 23:02 UTC 에 첫 대응 팀이 Orchestrator API 를 조회해 복제 토폴로지에 서부 데이터센터 서버만 남은 것을 확인했다."},"components":["database","failover-manager","database-replication","database-backup","network-link"],"root_cause":"dns-network","remediation":[{"text":{"en":"Locked the internal deployment tooling to stop further changes","ko":"23:07 UTC 에 내부 배포 도구를 수동으로 잠가 더는 변경이 들어오지 않게 했다."}},{"text":{"en":"Paused webhook delivery and GitHub Pages builds to stop jobs writing metadata","ko":"푸시 같은 메타데이터를 쓰는 작업을 멈추려고 웹훅 전송과 GitHub Pages 빌드를 일시 중지했다."}},{"text":{"en":"Restored the affected MySQL clusters from backups into the US East Coast data center and let them replicate from the West Coast","ko":"10월 22일 06:51 UTC 까지 여러 클러스터를 미국 동부 데이터센터에서 백업으로 복원했고, 복원된 서버는 서부에서 새 데이터를 복제받기 시작했다."}},{"text":{"en":"Found ways to restore directly from the West Coast to get around the throughput limits of downloading from off-site storage","ko":"외부 저장소에서 내려받는 처리량 한계를 피하려고 서부 데이터센터에서 직접 복원하는 방법을 찾았다."}},{"text":{"en":"Moved all database primaries back to the US East Coast data center","ko":"11:12 UTC 에 모든 데이터베이스 프라이머리를 다시 미국 동부에 세웠다."}},{"text":{"en":"Spread read traffic over additional MySQL read replicas in the US East Coast public cloud so replication could catch up","ko":"미국 동부 퍼블릭 클라우드에 MySQL 읽기 복제본을 추가로 띄워 읽기 요청을 더 많은 서버로 나눴고, 복제본 전체의 사용률이 내려가 복제가 따라잡았다."}},{"text":{"en":"Failed over to the original topology once the replicas were in sync","ko":"복제본이 맞춰진 뒤 16:24 UTC 에 원래 토폴로지로 페일오버했다."}},{"text":{"en":"Processed the backlog of webhook events and Pages builds, pausing to raise an internal TTL after about 200,000 webhook payloads were dropped","ko":"밀린 웹훅 이벤트와 Pages 빌드를 처리했다. 웹훅 페이로드 약 200,000건이 내부 TTL 을 넘겨 버려진 것을 발견하자 처리를 멈추고 TTL 을 당분간 늘리는 변경을 배포했다."}}],"action_items":[{"text":{"en":"Reconcile the writes from the captured MySQL binary logs that were not replicated, automatically where possible and with user outreach otherwise","ko":"각 클러스터에서 서부로 복제되지 않은 쓰기를 담은 MySQL 바이너리 로그를 분석해, 자동으로 맞출 수 있는 쓰기와 사용자에게 연락해야 하는 쓰기를 가린다."}},{"text":{"en":"Make sure status updates can be published even when GitHub Pages builds are paused","ko":"GitHub Pages 빌드가 멈춘 상황에서도 공지 글을 낼 수 있게 한다."}},{"text":{"en":"Give more accurate public estimates of the time to repair","ko":"장애 중 복구 예상 시간을 더 정확하게 알린다."}},{"text":{"en":"Configure Orchestrator so it cannot promote database primaries across regional boundaries","ko":"Orchestrator 설정을 고쳐 데이터베이스 프라이머리가 리전 경계를 넘어 승격되지 않게 한다."}},{"text":{"en":"Move to a status reporting mechanism that shows the status of each component of the platform","ko":"녹색, 노란색, 빨간색 세 단계만 있던 상태 표시를 새 상태 보고 방식으로 옮겨 플랫폼 구성 요소마다 상태를 보여 준다."}},{"text":{"en":"Serve GitHub traffic from multiple data centers in an active/active/active design with N+1 redundancy at the facility level","ko":"여러 데이터센터에서 트래픽을 받는 active/active/active 구성으로 옮기는 일을 앞당겨, 시설 단위 N+1 이중화로 데이터센터 하나가 통째로 망가져도 사용자에게 영향이 없게 한다."}},{"text":{"en":"Test assumptions about the system more proactively","ko":"시스템이 기대는 가정을 더 적극적으로 시험한다."}},{"text":{"en":"Validate failure scenarios systematically, with investment in fault injection and chaos engineering tooling","ko":"장애 시나리오를 사용자에게 닿기 전에 검증하는 관행을 들이고, 장애 주입과 카오스 엔지니어링 도구에 투자한다."}}],"contributing":["automation-amplified","assumption-unverified"],"recurrence_of":null,"symptoms_text":{"en":"At 22:54 UTC on 21 October 2018 GitHub's internal monitoring started alerting on many faults at once. By 23:02 UTC the first responders saw that the replication topologies of many MySQL clusters were in an unexpected state: the Orchestrator API showed topologies made only of servers in the US West Coast data center. When connectivity came back, the application tier had immediately started sending writes to the new West Coast primaries.\n\nFor users, GitHub showed information that was out of date and inconsistent, and for most of the incident it did not deliver webhook events or build and publish GitHub Pages sites. Pages that had to write to a database over the cross-country link loaded slowly, because the applications in the US East Coast could not cope with the extra round-trip latency on most of their database calls.\n\nMoving the primaries back to the US East Coast was not safe. The West Coast clusters had taken application writes for nearly 40 minutes, and the East Coast servers held several seconds of writes that had never reached the West Coast, so each side had writes the other lacked. Restoring the clusters from backups took hours, mostly in downloading multiple terabytes from a remote blob storage service and in decompressing, checksumming, preparing and loading the files onto new MySQL servers.\n\nAfter all primaries were back in the East Coast at 11:12 UTC on 22 October, dozens of read replicas were still several hours behind the primary, and a request had a good chance of reading from one of them. The status page promised recovery in about two hours, from a linear estimate of replication progress, but the lag shrank along a power decay curve instead. As users in Europe and the US started their workday, write load grew and by 13:15 UTC the replication delay was growing rather than shrinking.\n\nWhen processing of the queued work was re-enabled, about 200,000 webhook payloads had outlived an internal TTL and were dropped.","ko":"2018년 10월 21일 22:54 UTC 에 내부 모니터링 시스템이 여러 시스템에서 장애가 났다는 알림을 쏟아내기 시작했다. 23:02 UTC 에 첫 대응 팀은 여러 데이터베이스 클러스터의 토폴로지가 예상하지 못한 상태라는 것을 확인했다.\n\nOrchestrator API 로 복제 토폴로지를 조회하자 미국 서부 데이터센터의 서버만 나왔다. 애플리케이션 계층은 연결이 돌아오자마자 서부의 새 프라이머리로 쓰기를 보내고 있었다.\n\n동부 데이터센터의 데이터베이스에는 서부로 복제되지 않은 몇 초 분량의 쓰기가 남아 있었다. 서부 클러스터는 40분 가까이 새 쓰기를 받았기 때문에 양쪽에 서로 없는 쓰기가 생겼고, 프라이머리를 동부로 안전하게 되돌릴 수 없었다.\n\n동부에서 돌아가는 애플리케이션이 서부 MySQL 에 쓰면서 대륙을 오가는 지연이 생겼다. 쓰기를 하는 페이지가 느리게 열렸고, 사용자는 오래되고 서로 맞지 않는 정보를 봤다. 장애 대부분의 시간 동안 웹훅 이벤트가 전달되지 않았고 GitHub Pages 사이트도 빌드되거나 게시되지 않았다.\n\n클러스터를 백업으로 복원하는 데 몇 시간이 걸렸다. 수 테라바이트를 원격 블롭 저장소에서 내려받고, 압축을 풀고, 체크섬을 확인하고, 새 MySQL 서버에 적재하는 데 시간 대부분이 들었다.\n\n10월 22일 11:12 UTC 에 프라이머리를 모두 동부로 되돌린 뒤에도 수십 대의 읽기 복제본이 프라이머리보다 몇 시간씩 뒤처져 있었다. 읽기 요청은 큰 복제본 풀에 나뉘어 들어갔으므로 요청마다 몇 시간 늦은 복제본에 닿을 가능성이 컸고, 사용자는 서로 맞지 않는 데이터를 봤다.\n\n대응 팀은 복제 텔레메트리로 따라잡는 시간을 선형으로 추정해 상태 페이지에 복구까지 두 시간이라고 적었다. 실제 따라잡기는 직선이 아니라 거듭제곱 꼴로 줄어드는 곡선을 따랐다. 유럽과 미국에서 업무가 시작되며 쓰기 부하가 늘자 13:15 UTC 에는 복제 지연이 오히려 커졌다.\n\n쌓인 웹훅 이벤트 500만 건 이상과 Pages 빌드 8만 건을 다시 처리하기 시작하자 웹훅 페이로드 약 200,000건이 내부 TTL 을 넘겨 버려졌다."},"updated_at":"2026-11-14T05:53:41.744333Z","url":"https://benchlet.com/incidentlib/app/cases/IL-0210","org_slug":"github"}