2018年10月21日,GitHub 发生了一次持续24小时的故障,本文是该公司的事后故障分析。主要原因是,当天两个数据中心之间的光纤断了43秒,导致主库与从库之间数据不同步,而且没法确定哪一个镜像是数据完整的,不得不从头做数据恢复。
原文地址:https://github.blog/2018-10-30-oct21-post-incident-analysis/
原文作者:jasoncwarner GitHub技术负责人
原文写于:Oct 30, 2018
hacker news上对此文的讨论:https://news.ycombinator.com/item?id=18341339
相关阅读:Gitlab 对 2017年1月31日 误删数据库事件的事后分析
译者:驱蚊器喵#ΦωΦ
翻译水平有限,有不通顺的语句,请见谅。
上周,GitHub 经历了一次事件,导致服务质量下降长达24小时11分钟。虽然我们平台的某些部分不受此事件影响,但多个内部系统受到影响,导致我们显示过时且不一致的信息。最终,用户数据没有丢失; 但是,数据库在几秒内的写入还在手动调整中。在事件过程中的大多数时间中,GitHub 无法处理 Webhook 触发事件和构建发布 GitHub Pages 网站。
我们所有 GitHub 的员工想真诚地为这次事件对你们每个人的影响深表歉意。我们了解您对 GitHub 的信任,我们为构建弹性系统而感到自豪,这些系统使我们的平台保持高可用性。但是在这次事件中,让你们失望了,我们深感抱歉。虽然我们无法解决 GitHub 平台长时间无法使用的问题,但我们可以解释导致此事件发生的缘由,我们吸取的教训以及我们作为公司采取的措施,更好地确保不再发生这种情况。
事故背景
GitHub 大多数面向用户的服务都在我们自己的数据中心设施中运行。数据中心的拓扑旨在提供健壮且可扩展的边缘网络,该网络由多个区域的数据中心运行,为我们的计算和存储工作负载提供强劲动力。尽管在此设计中物理和逻辑组件中内置了多层冗余,但站点仍然可能无法在一段时间内相互通信。
在 UTC 时间的10月21日22:52,更换发生故障的 100G 光学设备的日常维护工作,导致我们位于美国西海岸的网络中心与位于美国东海岸的主要数据中心之间的连接断开。虽然两处之间的连接在 43 秒内恢复正常,但这次短暂的中断引发了一系列事件,导致24小时11分钟的服务降级(service degradation)。
之前,我们曾讨论过如何使用MySQL 存储 GitHub 的元数据以及我们的MySQL高可用性方案。GitHub运行着多个MySQL集群,其大小从几百 GB 到几乎 5TB 不等,每个集群最多有几十个只读副本(read replicas)来存储不属于Git的元数据,因此我们的应用程序可以提供 pull requests 和 issues、管理身份验证、协调后台处理等原始Git对象存储之外的额外功能。应用程序各个部分的不同数据通过功能分片存储在不同的集群中。
为了大规模提高性能,我们的应用程序将写入请求直接发送到每个集群的相关主数据库,但在绝大多数情况下将读取请求委派给副本服务器的子集。我们使用Orchestrator来管理我们的MySQL集群拓扑,并处理自动故障转移。Orchestrator在此过程中考虑了许多变故,并建立在Raft之上以达成共识。Orchestrator可以实现应用程序无法支持的拓扑,因此必须注意要将Orchestrator的配置与应用程序级别的期望保持一致。
事件的时间线
2018 October 21 22:52 UTC
在以上描述的网络分区中,Orchestrator一直在我们的主数据中心中运行,根据Raft的共识,开始了一个取消选择领导权的过程。美国西海岸数据中心和美国东海岸公共云Orchestrator节点能够建立法定数量并开始对群集进行故障转移,以便将写入指向美国西海岸数据中心。Orchestrator继续组织美国西海岸数据库集群拓扑。当连接恢复后,我们的应用层立即开始将写入流量切换到西海岸站点新选举出的主节点。
美国东海岸数据中心的数据库服务器存在一段短暂时间的写入,但尚未复制到美国西海岸的设施。由于两个数据中心中的数据库集群现在都包含其余数据中心中不存在的写入,因此我们无法安全地将主要数据库故障转移到美国东海岸数据中心。
2018 October 21 22:54 UTC
我们内部的监控系统开始发出警报,表明系统遇到了大量故障。此时,有几位工程师响应并对传入的通知进行分类。到UTC时间的23:02,我们第一响应小组的工程师已经确定许多数据库集群的拓扑处于意外状态。通过查询Orchestrator API显示的数据库复制拓扑仅包含来自美国西海岸数据中心的服务器。
2018 October 21 23:07 UTC
到目前为止,响应团队决定手动锁定我们的内部部署工具,以防止引入任何其他更改。在UTC时间的23:09,响应团队将网站设置为黄色警报状态。此操作会自动将情况升级为活动事件,并向事件协调员发送警报。在UTC时间的23:11,事件协调员介入,两分钟后决定将状态更改为红色警报状态。
2018 October 21 23:13 UTC
据了解,此问题影响了多个数据库集群。来自GitHub数据库工程团队的其他工程师被呼叫(paged)到来。他们开始调查目前所处的状态,以确定需要采取哪些操作来手动配置,将美国东海岸数据库作为每个集群的主数据库,并重建复制拓扑。这项工作具有挑战性,因为到目前为止,西海岸数据库集群已经从我们的应用层获取了近40分钟的写入。此外,东海岸集群中存在几秒钟的写入,这些写入未被复制到西海岸,并阻止将新写入复制回东海岸。
保护用户数据的机密性和完整性是GitHub的首要任务。为了保证用户数据的安全,30 多分钟的美国西海岸数据中心数据写入让我们不得不考虑 failing-forward。然而,在东海岸运行的应用程序依赖于西海岸 MySQL 集群的写入信息,目前无法应对由于跨国往返而带来的额外延迟。这个决定将导致很多用户无法使用我们的服务。我们认为,为了确保用户数据的一致性,延长服务降级的时间是必要的。
2018 October 21 23:19 UTC
通过查询数据库集群的状态,我们显然需要停止运行有关写入元数据的作业,比如 push。但我们明确决定,通过暂停 webhook 交付和 GitHub Pages 构建来部分降低站点可用性,以避免危及到我们已经从用户收到的数据。换句话说,我们的策略是优先考虑数据的完整性,而不是站点可用性和恢复所需的时间。
2018 October 22 00:05 UTC
参与事件的响应团队工程师开始制定计划来解决数据不一致的问题,并实施MySQL的故障转移程序。我们的计划是从备份还原,同步两个站点中的副本,回退到稳定的服务拓扑,然后继续处理队列中的作业。我们更新了状态,以告知用户我们将执行内部数据存储系统中的故障控制转移。
虽然 MySQL 数据每四个小时备份一次,并会保留多年,但备份存储在远程公共云 blob 存储服务中。恢复多个 TB 级备份数据所需的时间需要几个小时。消耗的大部分时间用于从远程备份服务传输数据。将大型备份文件解压缩,校验和准备并加载到新配置的MySQL服务器上的过程花费了大部分时间。此过程每天至少进行一次测试,因此充分了解恢复时间的框架,但是直到此事件之前,我们从未需要从备份完全重建整个集群,而是依赖其他策略,例如延迟复制。
2018 October 22 00:41 UTC
此时已启动所有受影响的MySQL群集的备份过程,工程师正在监控进度。同时,多个工程师团队正在研究加快传输和恢复时间的方法,而不会进一步降低站点可用性或者导致数据损坏。
2018 October 22 06:51 UTC
美国东海岸数据中心,几个集群已完成从备份恢复,并开始从西海岸复制新数据。这导致通过跨国链接写入操作的页面加载时间变慢,但是如果读取请求落在新恢复的副本上,则从这些数据库集群读取的页面将返回最新结果。其他更大的数据库集群仍在恢复中。
我们的团队已经确定了直接从西海岸恢复的方法,以克服从异地存储下载导致的吞吐量限制,并且越来越相信恢复即将完成,建立健康的复制拓扑所需的时间取决于它需要多长时间来复制完成。这个估计是从我们可用的复制遥测中线性插值的,并且更新了状态页面,将我们预计的恢复时间设置为两小时。
2018 October 22 07:46 UTC
GitHub发布了一篇博文,提供了更多的故障详细信息。我们在内部使用的GitHub Pages,在几个小时前暂停了所有构建,因此发布GitHub Pages页面需要更多的资源消耗。对于推迟构建,我们深表歉意。我们打算更快地发步此消息,并确保我们可以在这些限制下发布更新。
2018 October 22 11:12 UTC
所有数据库主节点再次在美国东海岸建立。由于写入内容现在被引到与我们的应用层在同一物理数据中心的数据库服务器,这导致网站响应极其缓慢。虽然这大大提高了性能,但仍有数十个数据库读取副本比主数据库延迟了几个小时。这些延迟的副本导致用户在与我们的服务进行交互时看到不一致的数据。我们将读取负载分散到大量的只读副本中,并且每个对我们服务的请求都很有可能达到多个小时延迟的只读副本。
事实上,复制到最新数据所需的时间遵循功率衰减函数(power decay function)而不是线性轨迹(linear trajectory)。因为欧洲和美国的用户醒来并开始工作,导致我们的数据库集群的写入负载增加,因此恢复过程花费的时间比原先估计的要长。
2018 October 22 13:15 UTC
这时,GitHub.com上的流量负载接近峰值。事件响应小组就如何继续进行了讨论。很明显,复制延迟在增加。我们已经在美国东海岸公有云中配置额外的 MySQL 只读副本。一旦这些实例就绪,就可以更容易在更多服务器上分摊读取请求。减少跨副本聚合可以让复制更快赶上。
2018 October 22 16:24 UTC
一旦副本同步,我们就会对原始拓扑进行故障转移,解决直接的延迟/可用性问题。作为有意识地决定在较短的事件窗口中优先处理数据完整性的一部分,我们在开始处理积累的积压数据时保持服务为红色警报状态。
2018 October 22 16:45 UTC
在恢复的这个阶段,我们必须平衡积压数据所带来的负载,因为过多的通知可能会导致生态系统的其他系统发生过载,我们还要尽可能快地将服务恢复到 100%。这个时候队列中有超过 500 万个 Webhook 事件和 8 万个Pages 构建请求。
当我们重新启用处理这些数据时,我们处理了大约200,000个webhook有效负载,这些有效负载因为内部TTL超时而被丢弃。在发现这一情况后,我们暂停了处理,并推送了一次更新,暂时增加该TTL。
为避免进一步降低状态更新的可用性,我们一直处于降级状态,直到我们完成处理整个积压的作业,并确保我们的服务已明显恢复到正常的性能水平。
2018 October 22 23:03 UTC
所有待处理的webhooks和Pages构建处理完毕,确认了所有系统的完整性和正确操作。站点状态更新为绿色正常状态。
后续步骤
解决不一致的数据
在恢复过程中,我们捕获了 MySQL 二进制日志,其中包含我们在主站点中写入但未被复制到西海岸站点的数据。未复制到西海岸的写入数量相对较少。例如,我们最忙的一个集群在受影响期间有 954 个写入。我们目前正在分析这些日志,并确定哪些写入可以自行解决,哪些需要与用户进行确认。我们有多个团队参与了这项工作,并确定了有一类写入已经被用户重复操作并成功保留。我们的主要目标是保持用户数据的完整性和准确性。
沟通
我们希望在事件期间向您传达有意义的信息,我们根据积压数据的处理速度,对修复时间进行了多次公开估算。回想起来,我们的估算没有考虑所有的变数。我们对此造成的混乱感到抱歉,并将努力在将来提供更准确的信息。
技术举措
在此次分析过程中,我们确定了很多技术举措。随着我们继续在内部进行广泛的事故后分析,我们发现我们有更多的工作要做。
调整 Orchestrator 配置,以防止跨区域选举主数据库。Orchestrator 只会按照配置的参数运行,不管应用程序层是否支持这种拓扑变更。单个区域内的首领选举通常是安全的,但突然出现的跨国延迟是导致这次事故的主要因素。这是系统的紧急行为,因为我们之前没有遇到过这么大规模的内部网络分区。
我们已经建立了一个可以更快报告状态的机制,可以更清晰地谈论事故的进展。尽管 GitHub 的很多部分在事故期间仍然可用,但我们只能将状态设置为绿色、黄色和红色。我们意识到,这并不能让我们准确了解哪些部分在正常运行,哪些部分出现了故障,在将来,我们还将显示平台的不同组件,这样就可以了解每项服务的状态。
在事故发生前的几周,我们启动了一项全公司范围的工程计划,通过多活设计让多个数据中心为 GitHub 流量提供支持。这个项目的目标是在设施层面支持 N+1 冗余,在不影响用户的情况下容忍单个数据中心故障。这是一项很重要的工作,需要一些时间。我们相信这种跨地理位置相连接的网站可以提供了一系列良好的权衡。这次事故加剧了这个项目紧迫性。
我们将在验证我们的假设方面采取更积极主动的立场。GitHub 是一家快速发展的公司,在过去十年中已经具备了一定程度的复杂性。随着公司不断的发展,捕捉和转移权衡和决策的历史负担将变得越来越困难。
组织协作
这次事故导致我们对站点可靠性的看法发生了转变。我们已经意识到,更严格的运维控制或改进的响应时间对于像我们这样复杂的服务系统的站点可靠性来说仍然是不够的。我们还将启动一种系统性实践,在故障场景有可能对用户产生影响之前对其进行验证,我们将在故障注入和混合工程方面进行投入。
结语
我们知道您的项目和企业有多依靠 GitHub。我们服务的可用性和您们数据的正确性备受关注。我们将继续分析这次事件,以便有机会为您们提供更好的服务,并不负寄予我们的信任。