回顾VS Online八月十四日的故障

[原文发表地址] Retrospective on the Aug 14th VS Online outage

[原文发表时间] 8-22-2014 11:10 AM

上周四我们有一个很严重的故障持续了5个多小时。故障表现为性能极其的糟糕,以至于这个服务对于大多数人来说基本是不可用的(尽管采取了各种各样的缓解措施也只能断断续续的访问)。故障开始于UTC时间14:00,结束于UTC时间19:30前。从故障的持续时间和严重程度上来说,这是VS Online有史以来最严重的一次事故。

为此我们感到非常糟糕,我们也将继续致力于做好每一件我们能做的事以防止故障。我很抱歉此次故障所造成的问题。整个团队从周四到周日不知疲倦地工作来解决眼前的健康问题并且修复潜在的可能导致复发的缺陷。

正如你可能想到的,在过去的一周,我们非常努力的工作试图了解发生了什么以及我们做什么更改才能防止这样的事情再次发生。要想找到触发故障的确切证据往往是十分难的,但是你可以通过仔细的研究来学习更多的东西。

关于这一类的故障,我经常问一些问题,包括:

发生了什么?

实际情况是,一个核心SPS(共享平台服务)数据库不堪数据库更新的重负,并且开始严重排队所以阻止了调用者。由于SPS是身份认证和授权过程的一部分,所以我们不能只是完全忽视它 - 虽然我建议当它变得非常缓慢的时候可以那样做,即使我们绕过一些授权检查以保证服务的及时响应,这也并不意味着就是世界末日。

触发器是什么?什么使它在今天发生了而不是昨天也不是其他任何一天?

虽然我们已经很努力的在解决这个问题了,但是仍然没有任何明确的答案(不过我们仍在继续)。我们知道,事发前,一些配置的更改导致“TFS”服务和“SPS”(共享平台服务)之间的通信量显著增加。这些通信量包括对已禁用的附加许可证的检测。我们也知道,大约在同一时间,我们看到了延迟的和未能交付的服务总线消息。我们相信其中一个或者两个就是关键触发因素,但是我们丢失了一些SPS数据库访问的日志,因此没办法100%确定。希望在接下来的几天里,我们会知道更多。

“根源”是什么?

在某种意义上讲它不同于触发器,触发器通常是一个导致一些连锁效应的条件。而根本原因更多的是理解为什么会级联以及为什么会把系统搞垮。我相信事实会证明,根本原因就是我们已经累积了一系列错误,才导致SPS数据库有很多额外的工作要做,同时系统也不稳定 – 从性能的角度来看。仅仅在系统上做几个操作 – 以额外的身份认证和许可证发放形式导致这些错误的连锁反应。其中大多数是在最近几次的Sprints引进的。下面是到目前为止我们发现并修复的核心错误列表:

  1. 大量从TFS到SPS的请求引起对“TFS服务”身份特性的不当更新。这将引起SQL写争论,同时无效的身份通过服务总线消息从SPS发送到TFS。此消息引起应用层缓存无效,随后的TFS请求就会发送一个请求到SPS,造成进一步的属性更新和一个恶性循环。
  2. 401中的错误处理代码将会对身份进行更新,这将导致身份的缓存无效-没有恶性的循环但是有许多额外的缓存刷新。
  3. Azure门户扩展服务里的一个错误每5秒就会触发一个401错误。
  4. 一个旧的行为从每个SPS AT引起同样的无效“事件”(用户1在AT1无效,用户2在AT2无效,那么用户1将收到两次失效)。我们有四个AT,所以这将有一个很严重的乘法效应。

我们还找到/修复了几个“加重的”错误,它们使得形势更加恶化,但不会坏到对它们自身引起严重的问题:

  1. 许多易变的属性也被存储在身份的扩展属性中,这会造成重复的缓存失效,大量的“更改推送通知”将被发送给不在乎属性更改的监听器。
  2. 有几个地方即使没有更改也会更新属性,如此便会造成没必要的失效和SQL旅程。

所有的这些,在某种形式下都随着对系统身份的更改而改变,这将会导致广播更改推送通知(有些情况下会过分传播),同时引起额外的进程/更新/缓存失效。这是不稳定的,因为这些身份更新中任何导致不期望的负载增长的东西,都会由于乘法效应和循环而无法控制。

从此次事件中我们学到了什么?

我一直想超越并理解底层模式。有时候也被称为“5个为什么”。事实上这也是列表中最重要的问题。为什么会发生这件事,我们能做些什么?而不是我们遇到了什么错误。为什么会出现这些错误?我们应该怎么做,以确保进入生产环境前,在设计/开发过程中就抓到这些问题?

先给大家讲个故事吧。这得从2008年说起,那时候我们首次将TFS横跨Microsoft的非常大的团队进行试运行,我们曾遇到一个灾难性事件。我们极度低估了成千上万的用户和大规模构建实验室将被放到TFS上这一负载。有将近九个月的时间我们如同活在地狱中一样,在严重的性能问题下,系统每天都在痛苦地减速,许多人给我发送厌恶的邮件。

从中我最大的收获就是,你绝对不能相信抽象概念里说的何时会遇到性能问题。在那种情况下,我们将SQL Server作为了一个关系数据库。最后我才知道真的不是这样的。它只是一个在磁盘I/O之上的软件抽象层。如果你不知道在磁盘I/O层发生了什么,你就什么也不会知道。你的无知也许是幸福的-但是当你遇到10x或者100x规模/性能需求的时候,你将完全精疲力尽。我们开始深深研究SQL磁盘布局,主要寻求,数据密度,查询计划等等。我们由上到下流动优化,确保我们能够了解所有CPU和 I/O的走向等。当我们做到这些时,TFS将扩到无比大的团队和代码库。

之后我们会做回归测试,这样会衡量更改,不仅仅及时而且是围绕着SQL旅程等等。

回到上周四…是我们草率了。这次马虎可能太严重了。对所有的团队来说,我们陷入了是自食苦果还是按客户需求增加功能的两难境地。在推动快节奏的过程中,估价每个Sprint等,我们允许了一些工程回归然后衰退-或者更确切地说,就是不把它带到我们正在写的新的代码中。我相信这是根本原因 - 开发者不能完全理解他们所做的一个更改的开销/影响,因为我们在跨软件/抽象层没有充分的可见性,而且当代码更改触发操作在整个资源耗费操作中显著增加时,我们也没有自动化的回归测试来标记。当然,你也可以在人工的测试环境中做这个,例如单元测试,也可以在生产环境中做,因为在你的测试中你抓不到任何东西。

所以,我们已经修了一些错误,在TFS和SPS交互方面还有不少的债务要进行处理,更重要的是我们需要加入一些基础建设,以便更好的在端到端开销上衡量和标记更改 – 在测试和生产中都可以。

讽刺(不是喜剧而是悲剧)的是最近团队才在这一点上有一些重新关注。几周之前,我们刚为小范围的团队人员准备了“编程马拉松”来试验新的想法。其中一个团队构建了一个解决方案的原型,用来捕获一个端到端请求的重要性能跟踪信息。在接下来的两周,我会试着写一篇博客来展示其中的一些想法。在此次事件前的那一周Buck(我们的开发总监)和我关于这个特殊的场景是否需要更多的投入曾进行过一次谈话。不幸的是我们还没有解决这个差距呢就发生了这个严重的事件。

我们将要做什么?

好的,我们学到了很多,但是关于此事我们到底要做什么呢。显然第一步是平息突发事件,让系统快速的回到可持续的健康状态。我想现在我们可以做到了。但是我们还没有解决根本原因。所以,下面是我们所做的计划,包括:

  1. 我们将分析所有SPS内部的,SPS和SQL之间的请求模式,构建正确的遥测技术和警报,以尽早捕获各种情况。添加基线到单元和功能测试,如此当开发人员check in代码的时候将不会更改基线。
  2. 划分测量SPS配置数据库将会成为高优先级的任务。启用了租户级别的主机后,我们就能够为每个租户划分身份相关的信息。这样我们就可以跨数据库分割SPS数据,在一个事件中启用更高的“上限”和更强的独立性,那么事情再也不会如此糟糕了。
  3. 我们正在研究为一个服务构建一个从慢速或者失败的依赖中节流和恢复自身的功能。我们应该为TFS和SPS交互使用同样的技术,让TFS 优雅的利用缓存状态或者失败。(这实际上也是我们几个月前或者更早的故障中遗留的一个行动项。)
  4. 我们应该测试我们关于服务总线提交滞后的设计,以便确保我们的功能继续工作或者缓慢降低。
  5. 看一看服务总线的API和分割主题,这将帮助我们更好的划分以便处理像身份认证这样的更“热门”的主题。

和之前一样,希望您能从我们犯的错误中获得一些有价值的详细信息。感谢您的支持。

Brian