第二章:定义非功能性需求
描述性能
大多数关于软件性能的讨论都考虑两种主要指标:
- 响应时间(Response time):从用户发出请求到收到所请求答案所经过的时间。度量单位是秒(或毫秒、微秒)。
- 吞吐量(Throughput):系统每秒处理的请求数或每秒处理的数据量。对于给定的硬件资源分配,存在一个可处理的最大吞吐量。度量单位是“某物/秒”。
在社交网络案例中,“每秒发布的帖子数(posts per second)”和“每秒写入时间线数(timeline writes per second)”是吞吐量指标,而“加载主页时间线所需时间(time it takes to load the home timeline)”和“帖子送达关注者所需时间(time until a post is delivered to followers)”是响应时间指标。
吞吐量和响应时间通常是相关的。图 2-3 示意了一个在线服务的这种关系。当请求吞吐量低时,服务响应时间低;但随着负载增加,响应时间增加。这是由于排队(queueing):当请求到达高度负载的系统时,CPU 可能正在处理较早的请求,因此传入的请求需要等待较早的请求完成。当吞吐量接近硬件所能处理的最大值附近时,排队延迟急剧增加。

图 2-3. 当服务的吞吐量接近其容量时,由于排队,响应时间急剧增加。
过载系统无法恢复的情况
如果系统接近过载,吞吐量被推到极限,有时会进入恶性循环,效率降低,从而变得更加过载。例如,如果有一个长队列的请求等待处理,响应时间可能增加到客户端超时并重新发送其请求。这会导致请求率进一步增加,使问题变得更糟——即重试风暴(retry storm)。即使负载再次降低,这样的系统可能仍处于过载状态,直到被重启或以其他方式重置。这种现象称为亚稳态故障(metastable failure),可能在生产系统中导致严重的中断 [7, 8, 9]。
防止重透过载
为了避免重试使服务过载,可以在客户端增加并随机化连续重试之间的时间(指数退避 [exponential backoff] [10, 11]),并暂时停止向最近返回错误或超时的服务发送请求(使用断路器 [circuit breaker] [12, 13] 或令牌桶算法 [token bucket algorithm] [14])。服务器也可以检测到接近过载并开始主动拒绝请求(负载丢弃 [load shedding] [15]),或发回要求客户端减慢速度的响应(背压 [backpressure] [1, 16])。排队和负载均衡算法的选择也会产生影响 [17]。
在性能指标方面,响应时间通常是用户最关心的,而吞吐量决定所需的计算资源(例如需要多少台服务器),从而决定服务特定工作负载的成本。如果吞吐量可能超过当前硬件的能力,则需要扩展容量;如果一个系统的最大吞吐量可以通过增加计算资源来显著提高,则称该系统具有可伸缩性(scalable)。
在本节中,我们将主要关注响应时间,并在“可伸缩性”第 49 页回到吞吐量和可伸缩性。
延迟与响应时间
“延迟(Latency)”和“响应时间(Response time)”有时可以互换使用,但在本书中,我们将以特定方式使用这些术语及几个相关术语(如图 2-4 所示):
- 响应时间:客户端所看到的时间;包括系统中任何地方产生的所有延迟。
- 服务时间(Service time):服务主动处理客户端请求所用的时长。
- 排队延迟(Queueing delays):可能发生在流程中的多个点——例如,在收到请求后,它可能需要等待 CPU 可用才能被处理;或者响应数据包在通过网络发送之前可能需要缓冲,如果同一台机器上的其他任务正通过出站网络接口发送大量数据。
- 延迟:是请求未被主动处理的时间的总称——即它处于潜伏状态。特别是网络延迟(network latency) 或网络时延(network delay) 指的是请求和响应在网络中传输所花费的时间。

图 2-4. 响应时间、服务时间、网络延迟和排队延迟
在图 2-4 中,时间从左向右流动;每个通信节点显示为一条水平线,请求或响应消息显示为从一个节点到另一个节点的厚对角线箭头。你将在本书中经常遇到这种样式的图。
响应时间在不同请求之间可能有很大差异,即使你反复发出相同的请求。许多因素可以增加随机延迟——例如,到后台进程的上下文切换、网络数据包丢失和 TCP 重传、垃圾回收暂停、强制从磁盘读取的页面错误,或服务器机架中的机械振动 [18]。我们将在“超时与无界延迟”第 352 页更详细地讨论这个主题。
排队延迟通常占响应时间可变性的一大部分。由于服务器只能同时处理少量事物(例如受其 CPU 核心数量的限制),只需少量慢请求就能阻塞后续请求的处理——这种效应称为队头阻塞(head-of-line blocking)。即使这些后续请求的服务时间很快,客户端的整体响应时间也会因为等待先前请求完成而变慢。排队延迟不是服务时间的一部分,因此重要的是在客户端侧测量响应时间。
平均值、中位数与百分位数
由于响应时间随请求变化,我们不应将其视为单个数字,而应视为可测量的值分布。在图 2-5 中,每个灰色条代表对服务的一个请求,其高度显示该请求花费的时间。大多数请求都相当快,但偶尔的离群值需要更长的时间。网络延迟的变化也称为抖动(jitter)。

图 2-5. 平均值与百分位数:对服务的 100 个请求样本的响应时间
通常报告服务的平均响应时间(技术上来说是算术平均值(arithmetic mean),即所有响应时间之和除以请求数)。对于估计吞吐量限制,均值是有用的 [19]。然而,如果你想知道“典型”的响应时间,均值并不是一个很好的指标,因为它不能告诉你多少用户实际经历了那种延迟。
通常更好的方法是使用百分位数(percentiles)。如果你将响应时间列表从最快到最慢排序,中位数(median) 就是中点——例如,如果你的中位数响应时间为 200 ms,那么意味着有一半的请求在 200 毫秒(ms)内返回,有一半的请求花费更长的时间。这使得中位数成为知道用户通常需要等待多久的好指标。中位数也称为第 50 百分位数,有时缩写为 p50。
要了解离群值的严重程度,可以查看更高的百分位数:常见的第 95、99 和 99.9 百分位数(缩写为 p95、p99 和 p999)。例如,如果第 95 百分位响应时间为 1.5 秒,这意味着 95% 的请求花费少于 1.5 秒,而 5% 的请求花费 1.5 秒或更多。这在图 2-5 中有所说明。
高响应时间百分位数,也称为尾部延迟(tail latencies),很重要,因为它们直接影响用户对服务的体验。例如,Amazon 使用第 99.9 百分位数来描述内部服务的响应时间要求,尽管这只会影响千分之一的请求。这是因为具有最慢请求的客户往往是那些账户数据最多的用户,正如他们购买了多次——也就是说,他们是最有价值的客户[20]。让这些客户满意很重要,要确保网站对他们来说够快。优化99.99百分位数(最慢的万分之一请求)被认为成本太高,且对亚马逊来说收益不足。在非常高的百分位数下降低响应时间很困难,因为它们容易受到你无法控制的随机事件的影响,而且收益递减。
响应时间对用户的影响
快速的服务显然比慢速的服务对用户更好[21]。然而,要获得可靠的数据来量化延迟对用户行为的影响却出奇地困难。
一些经常被引用的统计数据并不可靠。例如,2006年Google报告称,搜索结果的响应时间从400毫秒增加到900毫秒,导致流量和收入下降20%[22]。然而,另一项2009年的Google研究报告显示,延迟增加400毫秒仅导致每天搜索量减少0.6%[23];同年,Bing发现加载时间增加两秒使广告收入减少4.3%[24]。这些公司的新数据似乎没有公开。
一项较新的Akamai研究声称,响应时间增加100毫秒会使电商网站的转化率降低高达7%[25];但仔细审视会发现,同一项研究显示,非常快的页面加载时间也与较低的转化率相关!这个看似矛盾的结果可以解释为:加载最快的页面往往是那些没有有用内容的页面(例如404错误页面)。然而,由于该研究没有区分页面内容与加载时间的影响,其结果可能没有意义。
雅虎随后进行的一项研究控制了搜索结果的质量,比较了快速加载与慢速加载搜索结果的点击率[26]。该研究报告称,当快慢响应之间的差异达到1.25秒或更多时,快速搜索的点击率高出20%–30%。
响应时间指标的运用
高百分位数在作为服务单个终端用户请求的一部分而被多次调用的后端服务中尤为重要。即使你并行调用这些后端服务,请求仍然需要等待最慢的并行调用完成。只需要一个慢速调用,就足以拖慢整个终端用户请求,如图2-6所示。即使只有一小部分后端调用是慢速的,如果终端用户请求需要多次后端调用,那么遇到慢速调用的几率就会增加,因此更高比例的终端用户请求最终会变慢(这种现象被称为尾部延迟放大[27])。
百分位数常被用于服务等级目标(SLO)和服务等级协议(SLA)中,作为定义服务预期性能和可用性的方式[28]。例如,SLO可以设定一个目标:服务的中位响应时间小于200毫秒,99百分位数小于1秒,并且至少99.9%的有效请求返回非错误响应。SLA是一种合约,规定了如果未达到SLO将如何处理(例如,客户可能有权获得退款)。至少基本思路是这样;在实践中,为SLO和SLA定义良好的可用性指标并非易事[29, 30]。
计算百分位数
如果你想为服务的监控仪表板添加响应时间百分位数,你需要持续高效地计算它们。例如,你可能希望保留最近10分钟内请求响应时间的滚动窗口。每分钟,你计算出该窗口内数值的中位数和各种百分位数,并将这些指标绘制在图表上。
最简单的实现是保留时间窗口内所有请求的响应时间列表,并每分钟对该列表进行排序。如果这对你来说效率太低,还有一些算法可以以极低的CPU和内存成本计算出百分位数的良好近似值。开源的百分位数估计库包括HdrHistogram[31]、t-digest[32, 33]、OpenHistogram[34]和DDSketch[35]。
注意
对百分位数进行平均(例如,为了降低时间分辨率或合并多台机器的数据)在数学上是无意义的。聚合响应时间数据的正确方法是累加直方图[36]。
可靠性和容错
每个人对某事是可靠还是不可靠都有直观的理解。对于软件,典型的期望包括:
- 应用程序执行用户期望的功能。
- 应用程序能够容忍用户犯错或意外使用软件。
- 在预期的负载和数据量下,其性能足够满足所需用例。
- 系统防止任何未经授权的访问和滥用。
如果所有这些合起来意味着“正确工作”,那么我们可以将可靠性大致理解为“即使出现问题,也能继续正确工作”。为了更精确地描述“出现问题”,我们将区分故障(Fault)和失效(Failure)[37, 38, 39]:
故障(Fault) 当系统的某个特定部分停止正确工作时发生——例如,单个硬盘发生故障,或单台机器崩溃,或系统依赖的外部服务中断。
失效(Failure) 当整个系统停止向用户提供所需服务时发生——换句话说,当系统未达到SLO时。
故障与失效之间的区别可能令人困惑,因为它们是同一事物,只是处于不同层级。例如,如果一个硬盘停止工作,我们说硬盘发生了失效;如果系统只包含这一块硬盘,那么它停止了提供所需服务,因此也发生了失效。但是,如果系统由多块硬盘组成,单块硬盘的失效从更大系统的角度来看只是故障,更大系统可以通过在另一块硬盘上存储数据副本来容忍该故障。
容错(Fault Tolerance)
如果一个系统在发生某些故障时仍能继续向用户提供所需服务,我们称该系统具有容错能力。如果系统无法容忍某个特定部分出现故障,我们将该部分称为单点故障(SPOF),因为该部分的一个故障会升级为整个系统的失效。
例如,在社交网络案例中,可能发生的故障是:在扇出过程中,更新物化时间线的一台机器崩溃或变得不可用。要使该过程具有容错能力,我们需要确保另一台机器可以接管此任务,既不会遗漏任何本应送达的帖子,也不会重复任何帖子。(这个想法被称为仅一次语义(exactly-once semantics),我们将在第12章详细讨论。)
容错总是针对特定数量的特定类型故障。例如,一个系统可能最多容忍两块硬盘同时故障,或最多容忍三个节点中的一个崩溃。容忍任意数量的故障是没有意义的;如果所有节点都崩溃了,那就无能为力了。如果整个地球(以及其上的所有服务器)被黑洞吞噬,容忍这种故障需要太空托管——祝你好运能获得预算批准。
反直觉
在这种容错系统中,通过故意触发故障来提高故障率是有意义的——例如,在不发出警告的情况下随机杀死单个进程。这被称为故障注入(fault injection)。许多关键缺陷实际上是由于不完善的错误处理导致的[40];通过故意引入故障,你确保容错机制不断被锻炼和测试,这可以增加你对故障在自然发生时能被正确处理的自信心。混沌工程(Chaos engineering)是一门旨在通过故意注入故障等实验来提高对容错机制信心的学科[41]。
尽管我们通常更倾向于容忍故障而不是防止故障,但在某些情况下,预防胜于修复(例如,因为没有修复方法)。例如,安全问题是这类情况;如果攻击者破坏了系统并获取了敏感数据,该事件是无法撤销的。不过,本书主要处理那些可以修复的故障类型,如下几节所述。
硬件故障和软件故障
当我们想到系统失效的原因时,硬件故障很快浮现在脑海中:
- 磁硬盘每年约有2%–5%的故障率[42, 43];在拥有10,000块磁盘的存储集群中,因此我们平均每天应预期一块磁盘故障。最新数据表明磁盘变得更可靠,但故障率仍然显著[44]。
- 固态硬盘(SSD)每年约有0.5%–1%的故障率[45]。少量位错误会自动纠正[46],但即使是相当新的硬盘(即磨损较少的硬盘),每年每块硬盘也会发生大约一次不可纠正的错误。该错误率高于磁硬盘[47, 48]。
- 其他硬件组件(如电源、RAID控制器和内存模块)也可能发生故障,尽管频率低于硬盘[49, 50]。
- 大约每1000台机器中就有1台机器的CPU核心偶尔会计算出错误结果,这很可能是由于制造缺陷导致的[51, 52, 53]。在某些情况下,错误的计算会导致崩溃,但在其他情况下,它只会导致程序返回错误的结果。
- RAM中的数据可能因随机事件(如宇宙射线)或永久性物理缺陷而损坏。即使使用带纠错码(ECC)的内存,在给定年份中仍有超过1%的机器遇到不可纠正的错误,这通常会导致机器崩溃,并且受影响的内存模块需要更换[54]。此外,某些病态的内存访问模式可以以高概率翻转比特位[55]。
- 整个数据中心可能变得不可用(例如,由于停电或网络配置错误),甚至可能被永久摧毁(例如,由于火灾、洪水或地震[56])。太阳风暴——当太阳喷射出大量带电粒子时,会在长距离电线中感应出大电流——可能损坏电网和海底网络电缆[57]。虽然这种大规模故障很少见,但如果服务无法容忍失去一个数据中心,其影响可能是灾难性的[58]。
这些事件足够罕见,在小型系统上工作时,你通常不需要担心,只要你能轻松更换出现故障的硬件。然而,在大规模系统中,硬件故障发生得足够频繁,以至于它们成为正常系统运行的一部分。
通过冗余容忍硬件故障
我们对不可靠硬件的第一反应通常是向各个硬件组件添加冗余,以降低系统的故障率。磁盘可以配置为RAID(将数据分布在同一台机器的多个磁盘上,这样磁盘故障不会导致数据丢失),服务器可以有双电源和热插拔CPU,数据中心可以有电池和柴油发电机作为备用电源。这种冗余通常可以让一台机器不间断运行多年。
当组件故障是独立的时候,冗余最有效——也就是说,一个故障的发生不会改变另一个故障发生的可能性。然而,经验表明组件故障之间存在显著的相关性[43, 59, 60]。整个服务器机架或整个数据中心不可用的发生频率比我们期望的要高。
硬件冗余提高了单台机器的正常运行时间;然而,正如“分布式系统与单节点系统”(第19页)中讨论的,使用分布式系统具有优势,例如能够容忍一个数据中心的完全中断。因此,云系统往往较少关注单个机器的可靠性,而是通过在软件层面容忍故障节点来使服务高度可用。云提供商使用可用区来标识哪些资源在物理上位于同一位置;同一位置的资源比地理上分离的资源更可能同时故障。
本书中讨论的容错技术旨在容忍整个机器、机架或可用区的丢失。它们通常通过允许一个数据中心的机器在另一个数据中心的机器故障或不可达时接管其工作来工作。我们将在第6章、第10章以及本书的其他多个地方讨论此类容错技术。
能够容忍整台机器丢失的系统也具有操作上的优势。单服务器系统如果需要重启机器(例如,应用操作系统安全补丁),则需要计划停机;而多节点容错系统可以通过一次重启一个节点来打补丁,而不会影响用户的服务。这被称为滚动升级,我们将在第5章进一步讨论。
软件故障
虽然硬件故障可能弱相关,但它们仍然大多是独立的——例如,如果一个磁盘故障,同一台机器中的其他磁盘很可能没事,至少在一段时间内没事。另一方面,软件故障通常高度相关,因为许多节点运行相同的软件,因此具有相同的错误是很常见的[61, 62]。这类故障更难预测,并且往往导致比不相关的硬件故障更多的系统故障[49]。示例如下:
- 一个软件错误导致所有节点在特定情况下同时故障。例如,2012年6月30日,闰秒导致许多Java应用程序同时挂起,原因是Linux内核中的一个错误,导致几个互联网服务宕机[63]。另一个例子:由于固件错误,某些型号的所有SSD在精确运行32,768小时(不到四年)后突然故障,导致其上的数据无法恢复[64]。
- 一个失控的进程消耗了共享的有限资源,如CPU时间、内存、磁盘空间、网络带宽或线程[65]。例如,一个进程在处理大请求时消耗过多内存可能被操作系统杀死,或者客户端库中的错误可能导致比预期高得多的请求量[66]。
- 系统依赖的服务变慢、变得无响应或开始返回损坏的响应。
- 不同系统之间的交互导致涌现行为,而这种行为在单独测试每个系统时不会出现[67]。
- 级联故障,其中一个组件的问题导致另一个组件过载并变慢,进而导致另一个组件宕机[68, 69]。
导致这类软件故障的错误通常潜伏很长时间,直到被一组不寻常的情况触发。在这些情况下,软件暴露了它对环境的某种假设——虽然该假设通常成立,但出于某种原因,它最终不再成立[70, 71]。
软件中的系统性故障问题没有快速解决方案。许多小事可以提供帮助:仔细思考系统中的假设和交互;彻底测试;确保进程隔离;允许进程崩溃并重启;避免反馈循环,如重试风暴(见“当过载系统无法恢复”,第38页);在生产环境中测量、监控和分析系统行为。
人与可靠性
人类设计和构建软件系统,而维持系统运行的运维人员也是人。与机器不同,人类不仅遵循规则;他们的优势之一是在完成工作时具有创造性和适应性。然而,这种特性也导致了不可预测性,有时会导致尽管出于好意,但也会因错误而引发故障。例如,一项对大型互联网服务的研究发现,运维人员的配置变更才是宕机的主要原因,而硬件故障(服务器或网络)仅在10%–25%的案例中起作用[72]。
人们很容易将此类问题标记为“人为错误”,并希望通过更严格的流程和遵守规则来更好地控制人类行为,从而解决它们。然而,将错误归咎于人会适得其反。我们所谓的“人为错误”实际上并不是事件的真正原因,而是社会技术系统存在问题的症状,在这个系统中,人们正尽力完成他们的工作[73]。通常,复杂系统具有涌现行为,其中组件之间意想不到的交互也可能导致故障[74]。
各种技术措施可以帮助减少人为错误的影响,包括:彻底测试(手写测试和大量随机输入的属性测试)[40]、快速回滚配置更改的回滚机制、新代码的逐步发布、详细清晰的监控、用于诊断生产问题的可观测性工具(见“分布式系统的问题”,第20页),以及设计良好的界面,鼓励“正确操作”并阻止“错误操作”。
然而,所有这些都需要投入时间和金钱,在务实的日常业务现实中,组织往往优先考虑创收活动,而不是增加系统对错误弹性的措施。在更多功能和更多测试之间做选择时,许多组织理所当然地选择功能。然后,当不可避免的可预防错误发生时,指责犯错误的人并没有意义;问题出在组织的优先级上。
越来越多的组织正在采用无责事后分析文化:事件发生后,鼓励相关人员分享所发生事件的完整细节,而不必担心受到惩罚,因为这允许组织中的其他人学习如何防止类似问题在未来发生[75]。这个过程可能会揭示出需要改变业务优先级、投资于被忽视的领域、改变相关人员的激励措施,或引起管理层对另一个系统性问题的关注。
作为一般原则,在调查事件时,你应该对简单的答案持怀疑态度。“鲍勃在部署那个变更时应该更小心”是没有建设性的,但“我们必须用Haskell重写后端”也一样。相反,管理层应该借此机会从每天与该系统打交道的人的角度,了解社会技术系统的细节,并根据这些反馈采取改进措施[73]。
可靠性有多重要?
可靠性不仅仅适用于核电站和空中交通管制;更普通的应用程序也被期望可靠地工作。业务应用程序中的错误会导致生产力下降(如果数字报告不正确,还会带来法律风险),电子商务网站的宕机可能导致巨大的收入损失和声誉损害。
在许多应用中,几分钟甚至几小时的临时停机是可以容忍的[76],但永久性数据丢失或损坏将是灾难性的。想象一下,一位父母将他们孩子的所有照片和视频都存储在你的照片应用程序中[77]。如果该数据库突然损坏,他们会作何感想?他们知道如何从备份中恢复他们的收藏吗?
作为不可靠软件如何伤害人们的另一个例子,考虑一下邮局地平线丑闻。在1999年至2019年间,英国管理邮局分支机构的数百人因会计软件显示其账户出现亏空而被判盗窃或欺诈罪。最终,人们清楚地认识到,许多这些亏空是由于软件错误造成的,导致许多定罪被推翻[78]。导致这可能是英国历史上最大的司法误判的原因是,英国法律假设计算机正确运行(因此,由计算机生成的证据是可靠的),除非有证据证明并非如此。
存在相反的证据[79]。软件工程师可能会嘲笑软件永远不可能完全没有错误的观点,但这对于那些因不可靠计算机系统导致的错误定罪而被冤枉入狱、宣告破产甚至自杀的人们来说,几乎毫无安慰。
在某些情况下,我们可能会选择牺牲可靠性以降低开发成本(例如,在为未经验证的市场开发原型产品时)——但我们应该非常清楚何时在偷工减料,并牢记潜在后果。
可扩展性
即使一个系统今天运行可靠,也不意味着它在未来必然可靠。退化的一个常见原因是负载增加。系统可能从10,000个并发用户增长到100,000个并发用户,或从100万增长到1000万。它可能处理的数据量比以前大得多。
可扩展性是我们用来描述系统应对负载增长能力的术语。
有时,在讨论可扩展性时,人们会发表类似这样的言论:“你不是谷歌或亚马逊。别担心规模问题了,直接用关系型数据库吧。”这条格言是否适用于你,取决于你所构建的应用类型。
如果你正在构建一个目前用户数很少的新产品(例如在初创公司),主要的工程目标通常是保持系统尽可能简单和灵活,以便你能在了解客户需求后轻松修改和调整产品功能[80]。在这样的环境中,为未来可能需要的假设性规模而担心是适得其反的。最好的情况下,对可扩展性的投资是浪费精力和过早优化;最坏的情况下,它会把你锁定在一个不灵活的设计中,使应用更难演化。
可扩展性并不是一个一维的标签——说“X是可扩展的”或“Y不可扩展”是没有意义的。相反,讨论可扩展性意味着考虑以下问题:
- 如果系统以特定方式增长,我们有哪些应对增长的选择?
- 我们如何增加计算资源来处理额外的负载?
- 根据当前的增长预测,我们何时会达到当前架构的极限?
如果你成功让应用变得流行,从而处理不断增长的负载,你将了解性能瓶颈所在,以及需要在哪些维度上进行扩展。那时,才是开始考虑可扩展性技巧的时候。
理解负载
首先,你需要清晰理解系统当前的负载。只有这样,你才能讨论增长问题(“如果负载翻倍会怎样?”)。这通常是对吞吐量的衡量——例如,服务的每秒请求数、每天到达的新数据量(以GB计)、或每小时购物车结账次数。有时你关心的是变化量的峰值,例如社交网络案例中同时在线用户的数量。
通常,负载的其他统计特征会影响访问模式,从而影响可扩展性需求。例如,你可能需要知道数据库的读写比、缓存命中率、或每个用户的数据项数量(在案例中为粉丝数)。也许对你重要的是平均情况,或者瓶颈由少数极端情况主导。这完全取决于特定应用的细节。
一旦你了解了系统负载,就可以研究负载增加时会发生什么。你可以从两个方面来看待这个问题:
- 当你以特定方式增加负载,同时保持系统资源(CPU、内存、网络带宽等)不变时,系统的性能会受到怎样的影响?
- 当你以特定方式增加负载时,如果要保持性能不变,你需要增加多少资源?
通常的目标是使系统性能保持在SLA的要求范围内(参见第41页的“响应时间指标的使用”),同时最小化运行系统的成本。所需的计算资源越大,成本越高。某些类型的硬件可能比其他类型更具成本效益,并且这些因素可能随时间变化,因为新类型的硬件变得可用。
如果资源翻倍能使你在保持性能不变的情况下处理两倍的负载,那么我们就说实现了线性可扩展性,这被认为是好事。偶尔,由于规模经济或峰值负载的更好分布,处理两倍负载所需的资源可能少于翻倍[81, 82]。更有可能的是,成本增长速度快于线性增长。低效可能有很多原因;例如,如果数据量很大,处理单个写入请求可能比数据量小时涉及更多工作,即使请求大小相同。
共享内存、共享磁盘和共享无架构
增加服务硬件资源的最简单方法是将其迁移到更强大的机器上。单个CPU核心不再显著变快,但你可以购买(或租用云实例)具有更多CPU核心、更多RAM和更多磁盘空间的机器。这种方法称为垂直扩展或向上扩展。你可以通过使用多个进程或线程在单台机器上获得并行性。属于同一进程的所有线程可以访问相同的RAM,因此这种方法也称为共享内存架构。共享内存方法的问题在于成本增长速度快于线性;一台高端机器拥有低端机器两倍的硬件资源,其成本通常远高于两倍。而且由于瓶颈,该机器不太可能实际处理两倍的负载。
另一种方法是共享磁盘架构,它使用多台拥有独立CPU和RAM的机器,但将数据存储在一个由这些机器共享的磁盘阵列上,机器通过快速网络连接:网络附加存储(NAS)或存储区域网络(SAN)。这种架构传统上用于本地数据仓库工作负载,但争用和锁定的开销限制了共享磁盘方法的可扩展性[83]。
相比之下,共享无架构[84](也称为水平扩展或向外扩展)涉及一个包含多个节点的分布式系统,每个节点拥有自己的CPU、RAM和磁盘。节点之间的任何协调都在软件层面通过常规网络进行。
这种方法的优势(近年来已变得流行)在于:它有可能线性扩展,可以使用提供最佳性价比的硬件(尤其是在云中),可以更容易地随负载增加或减少调整硬件资源,并且可以通过将系统分布到多个数据中心和区域实现更高的容错性。缺点是需要显式分片(参见第7章),并带来分布式系统的所有复杂性(在第9章讨论)。
一些云原生数据库系统使用独立的存储和事务执行服务(参见第16页的“存储与计算分离”),多个计算节点共享对同一存储服务的访问。这种模型与共享磁盘架构有些相似,但它避免了旧系统的可扩展性问题。存储服务不提供文件系统(NAS)或块设备(SAN)抽象,而是提供一个专为数据库特定需求设计的专用API[85]。
可扩展性原则
大规模运行的系统架构通常是高度特定于应用的。不存在通用的、一刀切的可扩展架构(非正式称为魔法扩展秘方)。例如,设计用于处理每秒100,000个请求、每个请求1kB的系统,与设计用于每分钟3个请求、每个请求2GB的系统看起来非常不同——尽管两者的数据吞吐量相同(100MB/秒)。
此外,适合一种负载水平的架构不太可能应对10倍的负载。因此,如果你正在一个快速增长的服务上工作,很可能每次负载增加一个数量级时,都需要重新考虑架构。由于应用需求很可能会演化,提前规划超过一个数量级的未来扩展需求通常不值得。
一个良好的可扩展性通用原则是将系统分解为可以相对独立运行的小型组件。这是微服务(参见第21页的“微服务与无服务器”)、分片(第7章)、流处理(第12章)和共享无架构背后的基本原则。挑战在于知道应该在哪些事物之间划清界限:哪些应该在一起,哪些应该分开。微服务的设计指南可以在其他书籍中找到[86],我们将在第7章讨论共享无系统的分片。
另一个良好原则是不要把事情搞得比必要更复杂。如果单机数据库就能胜任,那么它可能比复杂的分布式设置更可取。自动伸缩系统(根据需求自动添加或移除资源)很酷,但如果你的负载相当可预测,手动伸缩系统可能带来更少的运维意外(参见第264页的“运维:自动重平衡与手动重平衡”)。有5个服务的系统比有50个服务的系统更简单。良好的架构通常涉及务实的混合方法。
可维护性
软件不会磨损或遭受材料疲劳,因此它不会像机械物体那样损坏。但应用的需求经常演化,软件运行的环境会变化(例如其依赖项和底层平台),并且可能包含需要修复的缺陷。
人们普遍认识到,软件的大部分成本并不在于其初始开发,而在于其持续的维护——修复缺陷、保持系统运行、调查故障、使其适应新平台、修改以应对新用例、偿还技术债务以及添加新功能[87, 88]。
维护可能很复杂,尤其是对于遗留系统。一个成功运行了很长时间的系统很可能使用了如今没有多少工程师了解的老旧技术(例如大型机和COBOL代码),而关于系统为何以特定方式设计的机构知识可能随着人员离开组织而丢失。修复他人的错误也可能是必要的。由于计算机系统常常与它们所支持的人类组织交织在一起,这类系统的维护既是一个人员问题,也是一个技术问题[89]。
我们今天创建的每个系统,如果它足够有价值能够长期存活,终有一天会成为遗留系统。为了尽量减少未来需要维护我们软件的人所承受的痛苦,我们应该在设计时就考虑可维护性。虽然我们无法总是预测哪些决策会在未来造成维护上的麻烦,但在本书中,我们将关注几个广泛适用的原则:
- 可操作性:让组织能够轻松保持系统平稳运行。
- 简洁性:通过使用易于理解的、一致的模式和结构来实现系统,并避免不必要的复杂性,让新工程师能够轻松理解系统。
- 可演化性:让工程师将来能够轻松地对系统进行修改,随着需求的变化,为未预见到的用例进行适配和扩展。
可操作性:让运维人员的生活更轻松
我们之前在“云时代的运维”(第17页)中讨论过运维的作用,并且我们看到,对于可靠的运维而言,人员流程与软件工具至少同样重要。事实上,有人提出“优秀的运维常常可以绕过糟糕(或不完整)软件的限制,但优秀的软件无法凭借糟糕的运维可靠运行”[62]。
在由数千台机器组成的大规模系统中,手动维护的成本高得离谱,因此自动化至关重要。然而,自动化可能是一把双刃剑。总会存在需要运维团队手动干预的边界情况(例如罕见的故障场景),而且由于无法自动处理的情况往往是最复杂的,更高的自动化程度需要更高技能的运维团队来解决这些问题[90]。
此外,一个出错的自动化系统通常比依赖操作员手动执行某些操作的系统更难排查问题。因此,更多的自动化并不总是意味着更好的可操作性。但一定程度的自动化是重要的——最佳平衡点取决于你特定应用程序和组织的具体情况。
良好的可操作性意味着让例行任务变得简单,让运维团队能够专注于高价值活动。数据系统可以通过以下方式提供帮助[91]:
- 允许监控工具检查系统的关键指标,并支持可观测性工具(参见“分布式系统的问题”第20页),以深入了解系统的运行时行为。各种商业和开源工具在此可以提供帮助[92]。
- 避免依赖单台机器(允许在系统整体继续不间断运行的同时,将机器下线进行维护)。
- 提供良好的文档和易于理解的运维模型(“如果我做了X,就会发生Y”)。
- 提供良好的默认行为,但同时也允许管理员在需要时覆盖默认值。
- 在适当的情况下进行自愈,但同时也在需要时允许管理员对系统状态进行手动控制。
- 表现出可预测的行为,尽量减少意外。
简洁性:管理复杂性
小型软件项目可以拥有非常简洁且富有表现力的代码,但随着项目变大,它们常常变得非常复杂且难以理解。这种复杂性拖慢了所有需要在系统上工作的人,进一步增加了维护成本。深陷复杂性的软件项目有时被描述为一团乱麻[93]。
当复杂性使维护变得困难时,预算和进度常常会被超出。在复杂的软件中,进行更改时引入缺陷的风险也更大。当系统对开发者来说更难以理解和推理时,隐藏的假设、未预见的后果和意外的交互更容易被忽略[71]。相反,降低复杂性能极大地改善软件的可维护性,因此简洁性应是我们构建系统的一个关键目标。
简单的系统更容易理解,所以我们应该以尽可能简单的方式解决给定的问题。不幸的是,这说起来容易做起来难。某个事物是否简单常常是一个主观问题,因为没有客观的简洁性标准[94]。例如,一个系统可能通过简单的接口隐藏复杂的实现,而另一个系统可能拥有简单的实现但向用户暴露了更多内部细节——哪一个更简单?
一种对复杂性的分析尝试将其分为两类:本质复杂性和偶然复杂性[95]。这个观点认为,本质复杂性是应用程序问题领域固有存在的,而偶然复杂性仅因我们工具的限制而产生。不幸的是,这种区分也是有缺陷的,因为随着工具的发展,本质与偶然之间的界限会发生移动[96]。
我们管理复杂性最好的工具之一就是抽象。一个好的抽象能够在简洁、易于理解的门面背后隐藏大量的实现细节。一个好的抽象还可以用于广泛的应用。这种复用不仅比多次重新实现类似的东西更高效,而且能带来更高质量的软件,因为抽象组件中的质量改进会惠及所有使用它的应用。
例如,高级编程语言是隐藏机器代码、CPU寄存器和系统调用的抽象。SQL是一种抽象,它隐藏了复杂的磁盘和内存数据结构、来自其他客户端的并发请求以及崩溃后的不一致性。当然,当用高级语言编程时,我们仍然在使用机器代码;我们只是不直接使用它,因为编程语言的抽象使我们不必考虑它。
旨在降低应用程序代码复杂性的抽象可以通过诸如设计模式[97]和领域驱动设计(DDD)[98]等方法创建。本书不讨论这种特定于应用的抽象,而是讨论你可以在其上构建应用的通用抽象,例如数据库事务、索引和事件日志。如果你想使用DDD等技术,你可以在本书描述的基础之上实现它们。
可演化性:让变更变得容易
你的系统需求永远保持不变是极不可能的。它们更可能处于不断的变化之中:你了解到新的事实,出现之前未预见到的用例,业务优先级发生变化,用户请求新功能,新平台取代旧平台,法律或监管要求发生变化,系统增长迫使架构变更等等。
在组织流程方面,敏捷工作模式提供了适应变化的框架。敏捷社区还开发了在频繁变化的环境中构建软件时有用的技术工具和流程,例如测试驱动开发(TDD)和重构。在本书中,我们寻找在由多个具有不同特征的应用程序或服务组成的系统层面增加敏捷性的方法。
修改数据系统并使其适应不断变化的需求的难易程度,与其简洁性和抽象性密切相关。松耦合、简单的系统通常比紧耦合、复杂的系统更容易修改。由于这是一个非常重要的思想,我们将使用一个不同的词来指代数据系统层面的敏捷性:可演化性[99]。
在大型系统中使变更变得困难的一个主要因素是不可逆性[100]。例如,假设你正在从一个数据库迁移到另一个数据库。如果在新系统出现问题时你无法回退到旧系统,那么风险比你可以轻松回退时要大得多。因此,不可逆的操作需要非常谨慎地进行。最小化不可逆性可以提高灵活性。
总结
在本章中,我们考察了几个非功能性需求的例子:性能、可靠性、可扩展性和可维护性。通过这些主题,我们还遇到了将在本书后续内容中相关的原则和术语。
我们从社交网络中实现主页时间线的一个案例研究开始,该案例说明了在规模扩大时出现的一些挑战。然后我们讨论了如何衡量性能(例如,使用响应时间百分位数)和系统负载(例如,使用吞吐量指标),以及这些指标如何在SLA中使用。可扩展性是一个紧密相关的概念:它侧重于确保当负载增长时性能保持不变。我们看到了一些可扩展性的一般原则,例如将任务分解为可以独立运行的更小部分,我们将在后续章节中深入探讨可扩展性技术的更多技术细节。
为了实现可靠性,你可以使用容错技术,即使一个组件(例如磁盘、机器或其他服务)出现故障,系统也能继续提供服务。我们看到了可能发生的硬件故障示例,并将它们与软件故障区分开来,软件故障由于通常具有很强的相关性而更难处理。实现可靠性的另一个方面是建立对人为错误的韧性,我们看到了无指责的事后剖析作为从事件中学习的一种技术。
最后,我们考察了可维护性的几个方面,包括支持运维团队的工作、管理复杂性以及使应用程序的功能易于随时间演化。如何实现这些目标没有简单的答案,但一种有帮助的方法是使用提供有用抽象的、经过充分理解的构建模块来构建应用程序。本书的其余部分将涵盖一系列在实践中被证明有价值的构建模块。
[1] Mike Cvet. “How We Learned to Stop Worrying and Love Fan-in at Twitter.” At QCon San Francisco, December 2016.
[2] Raffi Krikorian. “Timelines at Scale.” At QCon San Francisco, November 2012. Archived at perma.cc/V9G5-KLYK
[3] Twitter. “Twitter’s Recommendation Algorithm.” blog.x.com, March 2023. Archived at perma.cc/L5GT-229T
[4] Raffi Krikorian. “New Tweets per Second Record, and How!” blog.x.com, August 2013. Archived at perma.cc/6JZN-XJYN
[5] Jaz Volpert. “When Imperfect Systems Are Good, Actually: Bluesky’s Lossy Timelines.” jazco.dev, February 2025. Archived at perma.cc/2PVE-L2MX
[6] Samuel Axon. “3% of Twitter’s Servers Dedicated to Justin Bieber.” mashable.com, September 2010. Archived at perma.cc/F35N-CGVX
[7] Nathan Bronson, Abutalib Aghayev, Aleksey Charapko, and Timothy Zhu. “Metastable Failures in Distributed Systems.” At Workshop on Hot Topics in Operating Systems (HotOS), May 2021. doi:10.1145/3458336.3465286
[8] Marc Brooker. “Metastability and Distributed Systems.” brooker.co.za, May 2021. Archived at perma.cc/7FGJ-7XRK
[9] Lexiang Huang, Matthew Magnusson, Abishek Bangalore Muralikrishna, Salman Estyak, Rebecca Isaacs, Abutalib Aghayev, Timothy Zhu, and Aleksey Charapko. “Metastable Failures in the Wild.” At 16th USENIX Symposium on Operating Systems Design and Implementation (OSDI), July 2022.
[10] Marc Brooker. “Exponential Backoff and Jitter.” aws.amazon.com, March 2015. Archived at perma.cc/R6MS-AZKH
[11] Marc Brooker. “What Is Backoff For?” brooker.co.za, August 2022. Archived at perma.cc/PW9N-55Q5
[12] Michael T. Nygard. Release It!, 2nd edition. Pragmatic Bookshelf, 2018. ISBN: 9781680502398
[13] Frank Chen. “Slowing Down to Speed Up—Circuit Breakers for Slack’s CI/CD.” slack.engineering, August 2022. Archived at perma[14] Marc Brooker. “Fixing Retries with Token Buckets and Circuit Breakers.” brooker.co.za, February 2022. Archived at perma.cc/MD6N-GW26
[15] David Yanacek. “Using Load Shedding to Avoid Overload.” Amazon Builders’ Library, aws.amazon.com. Archived at perma.cc/9SAW-68MP
摘要 | 57
[16] Matthew Sackman. “Pushing Back.” wellquite.org, May 2016. Archived at perma.cc/3KCZ-RUFY
[17] Dmitry Kopytkov and Patrick Lee. “Meet Bandaid, the Dropbox Service Proxy.” dropbox.tech, March 2018. Archived at perma.cc/KUU6-YG4S
[18] Haryadi S. Gunawi, Riza O. Suminto, Russell Sears, Casey Golliher, Swaminathan Sundararaman, Xing Lin, Tim Emami, Weiguang Sheng, Nematollah Bidokhti, Caitie McCaffrey, Gary Grider, Parks M. Fields, Kevin Harms, Robert B. Ross, Andree Jacobson, Robert Ricci, Kirk Webb, Peter Alvaro, H. Birali Runesha, Mingzhe Hao, and Huaicheng Li. “Fail-Slow at Scale: Evidence of Hardware Performance Faults in Large Production Systems.” At 16th USENIX Conference on File and Storage Technologies, February 2018.
[19] Marc Brooker. “Is the Mean Really Useless?” brooker.co.za, December 2017. Archived at perma.cc/U5AE-CVEM
[20] Giuseppe DeCandia, Deniz Hastorun, Madan Jampani, Gunavardhan Kakulapati, Avinash Lakshman, Alex Pilchin, Swaminathan Sivasubramanian, Peter Vosshall, and Werner Vogels. “Dynamo: Amazon’s Highly Available Key-Value Store.” At 21st ACM Symposium on Operating Systems Principles (SOSP), October 2007. doi:10.1145/1294261.1294281
[21] Kathryn Whitenton. “The Need for Speed, 23 Years Later.” nngroup.com, May 2020. Archived at perma.cc/C4ER-LZYA
[22] Greg Linden. “Marissa Mayer at Web 2.0.” glinden.blogspot.com, November 2005. Archived at perma.cc/V7EA-3VXB
[23] Jake Brutlag. “Speed Matters for Google Web Search.” services.google.com, June 2009. Archived at perma.cc/BK7R-X7M2
[24] Eric Schurman and Jake Brutlag. “Performance Related Changes and Their User Impact.” Talk at Velocity 2009.
[25] Akamai Technologies, Inc. “The State of Online Retail Performance.” akamai.com, April 2017. Archived at perma.cc/UEK2-HYCS
[26] Xiao Bai, Ioannis Arapakis, B. Barla Cambazoglu, and Ana Freire. “Understanding and Leveraging the Impact of Response Latency on User Behaviour in Web Search.” ACM Transactions on Information Systems, volume 36, issue 2, article 21, April 2018. doi:10.1145/3106372
[27] Jeffrey Dean and Luiz André Barroso. “The Tail at Scale.” Communications of the ACM, volume 56, issue 2, pages 74–80, February 2013. doi:10.1145/2408776.2408794
[28] Alex Hidalgo. Implementing Service Level Objectives: A Practical Guide to SLIs, SLOs, and Error Budgets. O’Reilly Media, 2020. ISBN: 9781492076813
[29] Jeffrey C. Mogul and John Wilkes. “Nines Are Not Enough: Meaningful Metrics for Clouds.” At 17th Workshop on Hot Topics in Operating Systems (HotOS), May 2019. doi:10.1145/3317550.3321432
[30] Tamás Hauer, Philipp Hoffmann, John Lunney, Dan Ardelean, and Amer Diwan. “Meaningful Availability.” At 17th USENIX Symposium on Networked Systems Design and Implementation (NSDI), February 2020.
[31] Gil Tene. “HdrHistogram: A High Dynamic Range Histogram.” hdrhistogram.github.io/HdrHistogram
[32] Ted Dunning. “The t-digest: Efficient Estimates of Distributions.” Software Impacts, volume 7, article 100049, February 2021. doi:10.1016/j.simpa.2020.100049
[33] David Kohn. “How Percentile Approximation Works (and Why It’s More Useful than Averages).” timescale.com, September 2021. Archived at perma.cc/3PDP-NR8B
[34] Heinrich Hartmann and Theo Schlossnagle. “Circllhist—A Log-Linear Histogram Data Structure for IT Infrastructure Monitoring.” arXiv:2001.06561, January 2020.
[35] Charles Masson, Jee E. Rim, and Homin K. Lee. “DDSketch: A Fast and Fully-Mergeable Quantile Sketch with Relative-Error Guarantees.” Proceedings of the VLDB Endowment, volume 12, issue 12, pages 2195–2205, August 2019. doi:10.14778/3352063.3352135
[36] Baron Schwartz. “Why Percentiles Don’t Work the Way You Think.” solarwinds.com, November 2016. Archived at perma.cc/469T-6UGB
[37] Walter L. Heimerdinger and Charles B. Weinstock. “A Conceptual Framework for System Fault Tolerance.” Technical Report CMU/SEI-92-TR-033, Software Engineering Institute, Carnegie Mellon University, October 1992. Archived at perma.cc/GD2V-DMJW
[38] Felix C. Gärtner. “Fundamentals of Fault-Tolerant Distributed Computing in Asynchronous Environments.” ACM Computing Surveys, volume 31, issue 1, pages 1–26, March 1999. doi:10.1145/311531.311532
[39] Algirdas Avižienis, Jean-Claude Laprie, Brian Randell, and Carl Landwehr. “Basic Concepts and Taxonomy of Dependable and Secure Computing.” IEEE Transactions on Dependable and Secure Computing, volume 1, issue 1, pages 11–33, January 2004. doi:10.1109/TDSC.2004.2
[40] Ding Yuan, Yu Luo, Xin Zhuang, Guilherme Renna Rodrigues, Xu Zhao, Yongle Zhang, Pranay U. Jain, and Michael Stumm. “Simple Testing Can Prevent Most Critical Failures: An Analysis of Production Failures in Distributed Data-Intensive Systems.” At 11th USENIX Symposium on Operating Systems Design and Implementation (OSDI), October 2014.
摘要 | 59
[41] Casey Rosenthal and Nora Jones. Chaos Engineering. O’Reilly Media, 2020. ISBN: 9781492043867
[42] Eduardo Pinheiro, Wolf-Dietrich Weber, and Luiz Andre Barroso. “Failure Trends in a Large Disk Drive Population.” At 5th USENIX Conference on File and Storage Technologies (FAST), February 2007.
[43] Bianca Schroeder and Garth A. Gibson. “Disk Failures in the Real World: What Does an Mttf of 1,000,000 Hours Mean to You?” At 5th USENIX Conference on File and Storage Technologies (FAST), February 2007.
[44] Andy Klein. “Backblaze Drive Stats for Q2 2021.” backblaze.com, August 2021. Archived at perma.cc/2943-UD5E
[45] Iyswarya Narayanan, Di Wang, Myeongjae Jeon, Bikash Sharma, Laura Caulfield, Anand Sivasubramaniam, Ben Cutler, Jie Liu, Badriddine Khessib, and Kushagra Vaid. “SSD Failures in Datacenters: What? When? And Why?” At 9th ACM International on Systems and Storage Conference (SYSTOR), June 2016. doi:10.1145/2928275.2928278
[46] Alibaba Cloud Storage Team. “Storage System Design Analysis: Factors Affecting NVMe SSD Performance (1).” alibabacloud.com, January 2019. Archived at archive.org
[47] Bianca Schroeder, Raghav Lagisetty, and Arif Merchant. “Flash Reliability in Production: The Expected and the Unexpected.” At 14th USENIX Conference on File and Storage Technologies (FAST), February 2016.
[48] Jacob Alter, Ji Xue, Alma Dimnaku, and Evgenia Smirni. “SSD Failures in the Field: Symptoms, Causes, and Prediction Models.” At International Conference for High Performance Computing, Networking, Storage and Analysis (SC), November 2019. doi:10.1145/3295500.3356172
[49] Daniel Ford, François Labelle, Florentina I. Popovici, Murray Stokely, Van-Anh Truong, Luiz Barroso, Carrie Grimes, and Sean Quinlan. “Availability in Globally Distributed Storage Systems.” At 9th USENIX Symposium on Operating Systems Design and Implementation (OSDI), October 2010.
[50] Kashi Venkatesh Vishwanath and Nachiappan Nagappan. “Characterizing Cloud Computing Hardware Reliability.” At 1st ACM Symposium on Cloud Computing (SoCC), June 2010. doi:10.1145/1807128.1807161
[51] Peter H. Hochschild, Paul Turner, Jeffrey C. Mogul, Rama Govindaraju, Parthasarathy Ranganathan, David E. Culler, and Amin Vahdat. “Cores That Don’t Count.” At Workshop on Hot Topics in Operating Systems (HotOS), June 2021. doi:10.1145/3458336.3465297
[52] Harish Dattatraya Dixit, Sneha Pendharkar, Matt Beadon, Chris Mason, Tejasvi Chakravarthy, Bharath Muthiah, and Sriram Sankar. Silent Data Corruptions at Scale. arXiv:2102.11245, February 2021.
[53] Diogo Behrens, Marco Serafini, Sergei Arnautov, Flavio P. Junqueira, and Christof Fetzer. “Scalable Error Isolation for Distributed Systems.” At 12th USENIX Symposium on Networked Systems Design and Implementation (NSDI), May 2015.
[54] Bianca Schroeder, Eduardo Pinheiro, and Wolf-Dietrich Weber. “DRAM Errors in the Wild: A Large-Scale Field Study.” At 11th International Joint Conference on Measurement and Modeling of Computer Systems (SIGMETRICS), June 2009. doi:10.1145/1555349.1555372
[55] Yoongu Kim, Ross Daly, Jeremie Kim, Chris Fallin, Ji Hye Lee, Donghyuk Lee, Chris Wilkerson, Konrad Lai, and Onur Mutlu. “Flipping Bits in Memory Without Accessing Them: An Experimental Study of DRAM Disturbance Errors.” At 41st Annual International Symposium on Computer Architecture (ISCA), June 2014. doi:10.5555/2665671.2665726
[56] Tim Bray. “Worst Case.” tbray.org, October 2021. Archived at perma.cc/4QQM-RTHN
[57] Sangeetha Abdu Jyothi. “Solar Superstorms: Planning for an Internet Apocalypse.” At ACM SIGCOMM Conference, August 2021. doi:10.1145/3452296.3472916
[58] Adrian Cockcroft. “Failure Modes and Continuous Resilience.” adrianco.medium.com, November 2019. Archived at perma.cc/7SYS-BVJP
[59] Shujie Han, Patrick P. C. Lee, Fan Xu, Yi Liu, Cheng He, and Jiongzhou Liu. “An In-Depth Study of Correlated Failures in Production SSD-Based Data Centers.” At 19th USENIX Conference on File and Storage Technologies (FAST), February 2021.
[60] Edmund B. Nightingale, John R. Douceur, and Vince Orgovan. “Cycles, Cells and Platters: An Empirical Analysis of Hardware Failures on a Million Consumer PCs.” At 6th European Conference on Computer Systems (EuroSys), April 2011. doi:10.1145/1966445.1966477
[61] Haryadi S. Gunawi, Mingzhe Hao, Tanakorn Leesatapornwongsa, Tiratat Patana-anake, Thanh Do, Jeffry Adityatama, Kurnia J. Eliazar, Agung Laksono, Jeffrey F. Lukman, Vincentius Martin, and Anang D. Satria. “What Bugs Live in the Cloud? A Study of 3000+ Issues in Cloud Systems.” At 5th ACM Symposium on Cloud Computing (SoCC), November 2014. doi:10.1145/2670979.2670986
[62] Jay Kreps. “Getting Real About Distributed System Reliability.” blog.empathybox.com, March 2012. Archived at perma.cc/9B5Q-AEBW
[63] Nelson Minar. “Leap Second Crashes Half the Internet.” somebits.com, July 2012. Archived at perma.cc/2WB8-D6EU
[64] Hewlett Packard Enterprise. “Support Alerts—Customer Bulletin a00092491en_us.” support.hpe.com, November 2019. Archived at perma.cc/S5F6-7ZAC
[65] Lorin Hochstein. “Awesome Limits.” github.com, November 2020. Archived at perma.cc/3R5M-E5Q4
[66] Caitie McCaffrey. “Clients Are Jerks: AKA How Halo 4 DoSed the Services at Launch & How We Survived.” caitiem.com, June 2015. Archived at perma.cc/MXX4-W373
[67] Lilia Tang, Chaitanya Bhandari, Yongle Zhang, Anna Karanika, Shuyang Ji, Indranil Gupta, and Tianyin Xu. “Fail Through the Cracks: Cross-System Interaction Failures in Modern Cloud Systems.” At 18th European Conference on Computer Systems (EuroSys), May 2023. doi:10.1145/3552326.3587448
[68] Mike Ulrich. Addressing Cascading Failures. In Betsy Beyer, Jennifer Petoff, Chris Jones, and Niall Richard Murphy (ed). Site Reliability Engineering: How Google Runs Production Systems. O’Reilly Media, 2016. ISBN: 9781491929124
[69] Harri Faßbender. “Cascading Failures in Large-Scale Distributed Systems.” blog.mi.hdm-stuttgart.de, March 2022. Archived at perma.cc/K7VY-YJRX
[70] Richard I. Cook. “How Complex Systems Fail.” Cognitive Technologies Laboratory, April 2000. Archived at perma.cc/RDS6-2YVA
[71] David D. Woods. “STELLA: Report from the SNAFUcatchers Workshop on Coping with Complexity.” snafucatchers.github.io, March 2017. Archived at archive.org
[72] David Oppenheimer, Archana Ganapathi, and David A. Patterson. “Why Do Internet Services Fail, and What Can Be Done About It?” At 4th USENIX Symposium on Internet Technologies and Systems (USITS), March 2003.
[73] Sidney Dekker. The Field Guide to Understanding “Human Error”, 3rd edition. CRC Press, 2017. ISBN: 9781472439055
[74] Sidney Dekker. Drift into Failure: From Hunting Broken Components to Understanding Complex Systems. CRC Press, 2011. ISBN: 9781315257396
[75] John Allspaw. “Blameless PostMortems and a Just Culture.” etsy.com, May 2012. Archived at perma.cc/YMJ7-NTAP
[76] Itzy Sabo. “Uptime Guarantees—A Pragmatic Perspective.” world.hey.com, March 2023. Archived at perma.cc/F7TU-78JB
[77] Michael Jurewitz. “The Human Impact of Bugs.” jury.me, March 2013. Archived at perma.cc/5KQ4-VDYL
[78] Mark Halper. “How Software Bugs Led to ‘One of the Greatest Miscarriages of Justice’ in British History.” Communications of the ACM, volume 68, issue 3, pages 12–14, January 2025. doi:10.1145/3703779
[79] Nicholas Bohm, James Christie, Peter Bernard Ladkin, Bev Littlewood, Paul Marshall, Stephen Mason, Martin Newby, Steven J. Murdoch, Harold Thimbleby, and Martyn Thomas. “The Legal Rule That Computers Are Presumed to be Operating Correctly—Unforeseen and Unjust Consequences.” Briefing note, benthamsgaze.org, June 2022. Archived at perma.cc/WQ6X-TMW4
[80] Dan McKinley. “Choose Boring Technology.” mcfunley.com, March 2015. Archived at perma.cc/7QW7-J4YP
[81] Andy Warfield. “Building and Operating a Pretty Big Storage System Called S3.” allthingsdistributed.com, July 2023. Archived at perma.cc/7LPK-TP7V
[82] Marc Brooker. “Surprising Scalability of Multitenancy.” brooker.co.za, March 2023. Archived at perma.cc/ZZD9-VV8T
[83] Ben Stopford. “Shared Nothing vs. Shared Disk Architectures: An Independent View.” benstopford.com, November 2009. Archived at perma.cc/7BXH-EDUR
[84] Michael Stonebraker. “The Case for Shared Nothing.” IEEE Database Engineering Bulletin, volume 9, issue 1, pages 4–9, March 1986. perma.cc/P9YL-C4PS
[85] Panagiotis Antonopoulos, Alex Budovski, Cristian Diaconu, Alejandro Hernandez Saenz, Jack Hu, Hanuma Kodavalla, Donald Kossmann, Sandeep Lingam, Umar Farooq Minhas, Naveen Prakash, Vijendra Purohit, Hugh Qu, Chaitanya Sreenivas Ravella, Krystyna Reisteter, Sheetal Shrotri, Dixin Tang, and Vikram Wakade. “Socrates: The New SQL Server in the Cloud.” At ACM International Conference on Management of Data (SIGMOD), June 2019. doi:10.1145/3299869.3314047
[86] Sam Newman. Building Microservices, 2nd edition. O’Reilly Media, 2021. ISBN: 9781492034025
[87] Nathan Ensmenger. “When Good Software Goes Bad: The Surprising Durability of an Ephemeral Technology.” At The Maintainers Conference, April 2016. Archived at perma.cc/ZXT4-HGZB
[88] Robert L. Glass. Facts and Fallacies of Software Engineering. Addison-Wesley Professional, 2002. ISBN: 9780321117427
[89] Marianne Bellotti. Kill It with Fire. No Starch Press, 2021. ISBN: 9781718501188
[90] Lisanne Bainbridge. “Ironies of Automation.” Automatica, volume 19, issue 6, pages 775–779, November 1983. doi:10.1016/0005-1098(83)90046-8
[91] James Hamilton. “On Designing and Deploying Internet-Scale Services.” At 21st Large Installation System Administration Conference (LISA), November 2007.
[92] Dotan Horovits. “Open Source for Better Observability.” horovits.medium.com, October 2021. Archived at perma.cc/R2HD-U2ZT
[93] Brian Foote and Joseph Yoder. “Big Ball of Mud.” At 4th Conference on Pattern Languages of Programs (PLoP), September 1997. Archived at perma.cc/4GUP-2PBV
[94] Marc Brooker. “What Is a Simple System?” brooker.co.za, May 2022. Archived at perma.cc/U72T-BFVE
[95] Frederick P. Brooks. “No Silver Bullet—Essence and Accident in Software Engineering.” In The Mythical Man-Month, Anniversary edition, Addison-Wesley, 1995. ISBN: 9780201835953
[96] Dan Luu. “Against Essential and Accidental Complexity.” danluu.com, December 2020. Archived at perma.cc/H5ES-69KC
[97] Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley Professional, 1994. ISBN: 9780201633610
[98] Eric Evans. Domain-Driven Design: Tackling Complexity in the Heart of Software. Addison-Wesley Professional, 2003. ISBN: 9780321125217
[99] Hongyu Pei Breivold, Ivica Crnkovic, and Peter J. Eriksson. “Analyzing Software Evolvability.” At 32nd Annual IEEE International Computer Software and Applications Conference (COMPSAC), July 2008. doi:10.1109/COMPSAC.2008.50
[100] Enrico Zaninotto. “From X Programming to the X Organisation.” At XP Conference, May 2002. Archived at perma.cc/R9AR-QCKZ