{"id":"IL-0303","org":"GitHub","date":"2012-09-10","docs":[],"title":{"en":"GitHub's MySQL master failed over to a node with a cold buffer pool during a schema migration and GitHub.com went down","ko":"GitHub 의 새 3노드 MySQL 클러스터에서 자동 페일오버가 잘못 일어나고 클러스터 상태가 갈라져 GitHub.com 이 두 번 멈췄다"},"domain":["database"],"impact":{"scope":"GitHub.com had one hour and 46 minutes of downtime and another hour of significantly degraded performance over two outages; some events appeared on wrong dashboards and 16 private repositories were reachable by outsiders for seven minutes","users":"unknown","region":"unknown"},"review":{"written_by":"session","reviewed_at":null,"reviewed_by":null},"lessons":{"en":"GitHub concluded that automated failover is not the right strategy for its primary production database: for every failover in this incident, any member of the operations team would have said it should not happen. Failovers that must happen, in an emergency or during maintenance, should not hurt performance.","ko":"GitHub 은 이번 장애에 세 가지가 겹쳤다고 밝혔다. 일어나지 말았어야 할 'active' 역할 페일오버가 여러 번 일어났고, 클러스터 상태가 갈라져 클러스터 관리 소프트웨어가 잘못된 동작을 했으며, 그 페일오버들이 필요 이상으로 성능과 가용성을 해쳤다. 두 장애는 모두 자동 페일오버에서 시작했는데, 그 페일오버를 해야 하느냐고 운영팀 누구에게 물었어도 하지 말아야 한다고 답했을 것이다. GitHub 은 자동 페일오버가 서비스 가용성을 지키는 좋은 방법인 경우가 많지만, 주 운영 데이터베이스는 그런 경우가 아니라고 결론 내렸다."},"related":[],"sources":[{"url":"https://github.blog/news-insights/the-library/github-availability-this-week/","kind":"blog","archived":null}],"summary":{"en":"In mid-August 2012 GitHub replaced its pair of DRBD-backed MySQL servers with a three-node cluster. Pacemaker and Heartbeat manage two virtual IPs, one read/write and one read-only, and Percona Replication Manager, a resource agent for Pacemaker, moves the 'active' role (the master that takes reads and writes) and the 'standby' role (a read-only slave) between the nodes. MySQL runs on every node, so a failover only moves a virtual IP after flushing transactions and changing the read_only variable.\n\nOn September 10th a schema migration loaded the master so much that the replication manager's health checks failed; it moved the active role to another node and stopped MySQL on the original one. The new master had a cold InnoDB buffer pool, failed its health checks under the site's load, and the role moved back. The team enabled Pacemaker's maintenance-mode to stop all health checks and automatic failover, and performance recovered as the buffer pool warmed. Maintenance-mode also kept the standby from switching to its new master and replicating, so it returned incorrect results. On September 11th, an attempt to disable maintenance-mode caused a Pacemaker segfault and a partition of the cluster state; two master elections ran without coordination, and the out-of-date node 'c' became master by itself at 8:19 AM. The team powered it off at 8:26 AM, which took github.com down, could not change the cluster state afterwards, and so stopped Pacemaker and Heartbeat on nodes 'a' and 'b', started them on 'a', and started MySQL there in the active role. The site recovered as the buffer pool warmed.\n\nGitHub uses Redis to look up dashboard event stream entries and repository routes by ids that MySQL generates. For records created while node 'c' was master, those ids no longer matched, so some events appeared on the wrong users' dashboards and some new repositories were routed wrongly. 16 of those repositories were private and could be reached by people outside their collaborators and teams for seven minutes, from 8:19 AM to 8:26 AM PDT on September 11th. GitHub named the automated failover of its main production database as the root cause of both outages, which together caused one hour and 46 minutes of downtime and another hour of significantly degraded performance.","ko":"2012년 8월 중순 GitHub 운영팀은 DRBD 위에 돌던 오래된 MySQL 서버 한 쌍을 3노드 클러스터로 바꿨다. 클러스터는 애플리케이션에 가상 IP 두 개를 제공했다. 하나는 읽기 쓰기용, 하나는 읽기 전용이었고, 두 가상 IP 는 Pacemaker 와 Heartbeat 가 관리했다. 클러스터 안에서 'active' 와 'standby' 역할을 옮기는 MySQL 복제 조율은 Pacemaker 의 리소스 에이전트인 Percona Replication Manager 가 맡았다. 새 구성에서는 모든 노드에서 MySQL 이 늘 돌고 있어, 페일오버는 트랜잭션을 비우고 read_only 변수를 바꾼 뒤 가상 IP 를 옮기는 일이었다.\n\n9월 10일 월요일에 돌린 두 단계 무중단 스키마 마이그레이션은, 운영팀이 같은 종류의 마이그레이션에서 전에 본 것보다 높은 부하를 데이터베이스에 걸었다. Percona Replication Manager 의 헬스 체크가 마스터에서 실패했다. Percona Replication Manager 는 'active' 역할과 마스터를 다른 노드로 옮긴 뒤, 실패했다고 본 노드의 MySQL 을 멈췄다. 새 노드는 InnoDB 버퍼 풀이 비어 있어 느렸고, 그 부하로 헬스 체크가 다시 실패해 'active' 역할이 원래 노드로 되돌아갔다. 담당 엔지니어는 Pacemaker 의 maintenance-mode 를 켜서 모든 헬스 체크와 자동 페일오버를 멈췄고, 버퍼 풀이 차면서 사이트가 천천히 돌아왔다.\n\n9월 11일 아침, 'standby' 노드가 잘못된 쿼리 결과를 낸다는 신고가 들어왔다. maintenance-mode 때문에 이 노드가 복제를 다시 시작하지 못한 것이었다. maintenance-mode 를 끄려 하자 Pacemaker 에서 segfault 가 나 클러스터 상태가 갈라졌고, 혼자 남은 'c' 노드가 8:19 AM 에 마스터로 뽑혔다. 'c' 는 데이터가 뒤처진 노드였다. 운영팀은 8:26 AM 에 'c' 의 전원을 꺼서 갈라진 상태를 끝냈고, 이 때문에 운영 데이터베이스 접근과 github.com 접근이 모두 끊겼다. 그 7분 동안 MySQL 과 Redis 사이의 id 연결이 어긋나 일부 이벤트가 다른 사용자의 대시보드에 보였고, 비공개 저장소 16개가 협업자가 아닌 사람에게 열려 있었다. 운영팀은 'a' 와 'b' 노드에서 클러스터 상태를 되살리려 했지만 상태 변경이 반영되지 않았다. 결국 Pacemaker 와 Heartbeat 를 두 노드에서 모두 멈춘 뒤 'a' 에서만 다시 띄웠다. 'a' 에서 MySQL 이 'active' 역할을 맡았고 버퍼 풀이 차면서 사이트가 천천히 돌아왔다.\n\n두 번의 장애로 GitHub.com 은 1시간 46분 동안 멈췄고, 그와 별도로 1시간 동안 성능이 크게 떨어졌다. GitHub 은 두 장애의 근본 원인을 주 운영 데이터베이스의 자동 페일오버로 꼽았다."},"trigger":{"en":"A schema migration on Monday, September 10th put more load on the master database than earlier migrations had, and Percona Replication Manager's health checks on the master failed.","ko":"무중단 MySQL 스키마 마이그레이션이 이전의 같은 종류 마이그레이션보다 높은 부하를 데이터베이스에 걸었고, 그 부하로 Percona Replication Manager 의 헬스 체크가 마스터에서 실패했다."},"severity":"unknown","symptoms":["failover-failed","database-overloaded","high-latency","replication-lag","split-brain","service-unavailable"],"timeline":{"detected_after":null,"resolved_after":null},"date_kind":"incident","detection":{"en":"On the morning of September 11th, a developer told the operations team that the 'standby' node was returning incorrect query results.","ko":"9월 11일 아침 한 개발자가 'standby' 역할을 맡은 노드에서 잘못된 쿼리 결과가 나온다고 운영팀에 알렸다. 그 뒤 운영팀은 혼자 마스터로 뽑힌 'c' 노드가 앞서 데이터가 뒤처졌다고 판단한 노드라는 것을 알아챘다."},"components":["database","failover-manager","database-replication"],"root_cause":"software-bug","remediation":[{"text":{"en":"Enabled Pacemaker's maintenance-mode so no health checks or automatic failovers ran, and let the buffer pool warm","ko":"9월 10일 Pacemaker 의 maintenance-mode 를 켜서 모든 헬스 체크와 자동 페일오버 동작을 멈췄다."}},{"text":{"en":"Powered off the out-of-date node 'c' to end the partition","ko":"9월 11일 8:26 AM 에 데이터가 뒤처진 'c' 노드의 전원을 꺼서 갈라진 클러스터 상태를 끝내고 데이터가 더 어긋나지 않게 했다."}},{"text":{"en":"Stopped Pacemaker and Heartbeat on nodes 'a' and 'b' and started them on node 'a' only","ko":"클러스터 상태를 깨끗하게 되살리려고 'a' 와 'b' 두 노드에서 Pacemaker 와 Heartbeat 프로세스를 모두 멈춘 뒤 'a' 노드에서 다시 띄웠다."}},{"text":{"en":"Started MySQL on node 'a' in the active role","ko":"'a' 노드에서 MySQL 을 띄워 'active' 역할을 맡게 했다."}},{"text":{"en":"Removed the events leaked to wrong dashboards and audited the wrongly routed repositories","ko":"다른 사용자의 대시보드에 새어 나간 이벤트를 모두 지우고, 그 시간에 경로가 잘못 잡힌 저장소를 모두 점검했다."}},{"text":{"en":"Contacted the owners of the exposed private repositories","ko":"협업자가 아닌 사람에게 열려 있던 비공개 저장소 16개의 소유자에게 모두 직접 연락했다."}}],"action_items":[{"text":{"en":"Change the Pacemaker configuration so the active database role fails over only when an operations engineer starts it","ko":"Pacemaker 설정을 바꿔 'active' 데이터베이스 역할의 페일오버는 운영팀 사람이 시작할 때만 일어나게 한다."}},{"text":{"en":"Find a way to warm the InnoDB buffer pool of slave databases so failovers do not hurt performance","ko":"비상시나 정기 유지보수 때 페일오버를 해도 성능이 떨어지지 않도록, 복제본 데이터베이스의 InnoDB 버퍼 풀을 미리 데우는 여러 방법을 조사하고 시험한다."}},{"text":{"en":"Audit the Pacemaker and Heartbeat stack, focusing on the code path behind the segfault","ko":"화요일에 segfault 를 일으킨 코드 경로를 중심으로 Pacemaker 와 Heartbeat 스택 전체를 점검한다."}},{"text":{"en":"Run hardware tests on the server where the segfault happened","ko":"만일에 대비해 segfault 가 난 서버에서 강도 높은 하드웨어 시험을 한다."}}],"contributing":["automation-amplified","warm-cache-dependency","manual-action-worsened","overlapping-failures"],"recurrence_of":null,"symptoms_text":{"en":"On Monday, September 10th, 2012, during a schema migration, the health checks on GitHub's MySQL master failed and Percona Replication Manager moved the 'active' role to another node of the new three-node cluster. That node's InnoDB buffer pool was cold and it performed poorly, its health checks failed in turn, and the role failed back to the original server. GitHub.com went down.\n\nThe migration, run with GitHub's two-pass system for zero-downtime MySQL schema changes, put more load on the database than the operations team had seen in earlier migrations of this kind. When the health checks failed, the replication manager also stopped MySQL on the node it judged failed. On the new master, the site's query load against a cold cache made the health checks fail again.\n\nTo stop the failovers the team put Pacemaker into maintenance-mode, in which no health checks or automatic failovers run, and the site recovered slowly as the buffer pool warmed. The next morning a developer reported incorrect query results from the 'standby' node: maintenance-mode had also blocked the steps that should have pointed the standby at its new master and started replication.\n\nWhen the team tried to disable maintenance-mode on Tuesday, September 11th, Pacemaker segfaulted and the cluster state split, with nodes 'a' and 'b' rejecting most messages from node 'c' and the other way round, although the cluster was configured to need a majority before acting. Node 'c', the out-of-date one, was elected master on its own at 8:19 AM. The team powered it off at 8:26 AM to stop further data drift, which cut all production database access and took down all of github.com.","ko":"2012년 9월 10일 월요일, 스키마 마이그레이션이 데이터베이스에 높은 부하를 걸자 Percona Replication Manager 의 헬스 체크가 마스터에서 실패했다. Percona Replication Manager 는 'active' 역할을 다른 노드로 옮겼고, 새 노드가 느려지면서 GitHub.com 이 멈췄다.\n\n새로 'active' 역할을 맡은 노드는 InnoDB 버퍼 풀이 비어 있는 상태였다. 캐시가 빈 채로 사이트의 쿼리 부하를 받자 시스템 부하가 올라 헬스 체크가 다시 실패했고, 'active' 역할은 원래 노드로 되돌아갔다. 헬스 체크를 끈 뒤에는 버퍼 풀이 천천히 정상 수준까지 찼고, 그동안 사이트 성능도 천천히 돌아왔다.\n\n다음 날인 9월 11일 아침, 한 개발자가 'standby' 역할을 맡은 노드가 잘못된 쿼리 결과를 낸다고 운영팀에 알렸다. 전날 클러스터를 maintenance-mode 로 둔 탓에, 'standby' 노드가 복제 마스터를 바꾸고 복제를 시작하는 동작이 일어나지 못한 것이었다.\n\n운영팀이 maintenance-mode 를 끄려 하자 Pacemaker 에서 segfault 가 났고 클러스터 상태가 둘로 갈라졌다. 'a' 와 'b' 두 노드는 'c' 노드의 메시지를 대부분 거부했고, 'c' 노드도 두 노드의 메시지를 대부분 거부했다. 과반이 동의해야 움직이도록 설정했는데도 마스터 선출이 조율 없이 두 곳에서 동시에 일어났다. 두 노드 쪽에서는 선출이 중간에 끊기고 MySQL 이 멈췄다. 혼자 남은 'c' 쪽에서는 8:19 AM 에 'c' 가 마스터로 뽑혔다. 'c' 는 운영팀이 앞서 데이터가 뒤처졌다고 판단한 노드였다.\n\n갈라진 동안 MySQL 과 Redis 사이의 데이터가 어긋났다. GitHub 은 MySQL 이 자동으로 만든 id 로 Redis 에서 대시보드 이벤트 스트림과 저장소 경로를 찾는데, 이 시간에 만든 레코드는 MySQL 의 id 와 Redis 의 데이터가 서로 맞지 않게 되었다. 그 결과 일부 이벤트가 다른 사용자의 대시보드에 보였고, 이 시간에 만든 일부 저장소는 경로가 잘못 잡혔다."},"updated_at":"2026-11-14T12:33:55.700816Z","url":"https://benchlet.com/incidentlib/app/cases/IL-0303","org_slug":"github"}