一块磁盘的年化故障率(Annual Failure Rate)是 1%,存一份数据在上面,一年内丢失的概率就是 1%。做三副本呢?直觉告诉你概率变成了 0.01 的三次方——百万分之一。但这个直觉是错的。三副本并不是独立事件的简单连乘,中间还有一个关键变量:修复时间。第一块盘坏了之后,你有多少时间能把副本补回来?在修复完成之前第二块盘也坏了的概率是多少?第三块呢?这才是持久性计算的核心。
更麻烦的是,磁盘故障往往不是独立的。同一批次的磁盘可能有相同的固件缺陷,同一个机架的电源故障会同时击倒多块盘,同一个可用区的网络中断会让修复流量无法传输。这些相关故障(Correlated Failure)能把理论上的”11 个 9”打到实际的”4 个 9”。
本文从故障率模型出发,用马尔可夫链推导多副本和纠删码的持久性公式,然后加入相关故障的影响因子,分析三个真实数据丢失案例,最后给出一个可运行的持久性计算器和工程层面的提升策略。
适用范围 本文的持久性计算主要针对磁盘级存储系统(HDD/SSD),不涉及内存级或磁带级存储。涉及的故障率数据来自 Backblaze 2023 年报告和 Google 2007 年论文。数学推导基于连续时间马尔可夫链,假设指数分布的故障时间和修复时间。相关故障的建模使用简化的乘法因子方法,不涉及 Copula 或贝叶斯网络等高级相关性模型。
一、持久性 vs 可用性(概念辨析)
持久性(Durability)和可用性(Availability)是存储系统最常混淆的两个指标。AWS S3 标称 99.999999999%(11 个 9)的持久性和 99.99%(4 个 9)的可用性——两个数字差了七个数量级,说明它们衡量的完全不是同一件事。
定义
持久性衡量的是:数据一旦写入,将来是否还能读到。更准确地说,是在给定时间窗口内(通常是一年),数据不丢失的概率。“丢失”意味着所有副本或所有编码分片都损坏,数据无法恢复。
可用性衡量的是:在任意时刻尝试读写数据,能否成功。可用性关心的是当前能不能用,而不是数据是否还在。一个系统可以暂时不可用(比如正在故障转移),但数据并没有丢失。
关键区别
| 维度 | 持久性(Durability) | 可用性(Availability) |
|---|---|---|
| 核心问题 | 数据还在不在 | 数据现在能不能访问 |
| 时间尺度 | 年(甚至十年级别) | 分钟到小时 |
| 容忍的状态 | 不容忍任何永久丢失 | 容忍短暂不可用 |
| 计量方式 | P(一年内无数据丢失) | 正常运行时间 / 总时间 |
| 典型目标 | 99.999999999%(11 个 9) | 99.99%(4 个 9) |
| 恢复手段 | 副本重建、纠删码修复 | 故障转移、重试、降级读 |
| 失败后果 | 不可逆——数据永久消失 | 可逆——等故障恢复即可 |
一个直观的例子:三副本系统中,一块盘坏了,剩两个副本还能提供服务。此时可用性没有受到影响(降级但可读),持久性风险却上升了——因为现在只剩两个副本,再坏一块就只剩一个了。如果在修复完成之前又坏了一块,可用性仍然正常(还有一个副本可读),但持久性已经岌岌可危。直到第三块也坏了,数据才真正丢失,可用性也降为零。
这说明一件事:持久性的衰减是隐性的,它不会在监控仪表盘上直接报错,你需要主动去检测副本数量、修复进度和故障模式。
持久性的度量
持久性通常用”几个 9”来衡量。N 个 9 意味着年化数据丢失概率(Annual Data Loss Probability)是 10 的负 N 次方:
持久性 年化丢失概率 含义
9 个 9 10^(-9) = 0.000000001 每 10 亿个对象每年丢 1 个
11 个 9 10^(-11) 每 1000 亿个对象每年丢 1 个
6 个 9 10^(-6) = 0.000001 每 100 万个对象每年丢 1 个
对于大规模存储系统,即使 9 个 9 也不够。如果你存了 1000 亿个对象(S3 在 2021 年宣布存储了超过 100 万亿个对象),9 个 9 意味着每年丢 100 个对象。11 个 9 才能把这个数字降到 1。
二、故障率模型(AFR/MTBF/MTTF/浴盆曲线)
持久性计算的起点是磁盘的故障率模型。故障率决定了”多长时间坏一块盘”,这直接影响多副本或纠删码的修复窗口和数据丢失概率。
2.1 基本术语
平均故障间隔时间(Mean Time Between Failures, MTBF) 原本用于可修复系统,指的是两次故障之间的平均运行时间。对于磁盘这种通常不修复而是直接替换的设备,更准确的术语是平均失效前时间(Mean Time To Failure, MTTF)。但在工业界,MTBF 和 MTTF 经常混用,磁盘厂商标注的”MTBF 200 万小时”实际上指的是 MTTF。
年化故障率(Annual Failure Rate, AFR) 是指一块磁盘在一年内发生故障的概率。AFR 和 MTTF 的关系在指数分布假设下是:
AFR = 1 - e^(-8760 / MTTF)
其中 8760 是一年的小时数。当 MTTF 远大于 8760 小时时(磁盘通常如此),可以近似为:
AFR ≈ 8760 / MTTF
例如,一块标称 MTTF 为 200 万小时的企业级硬盘,理论 AFR 约为 8760 / 2000000 = 0.438%。
2.2 厂商标称 vs 实际故障率
厂商标称的 MTTF 和实际观测到的 AFR 之间存在巨大差距。这是持久性工程中最需要警惕的一点。
Backblaze 自 2013 年起每季度公布其数据中心的硬盘故障率统计。以 2023 年年度报告为例(覆盖约 27.5 万块运行中的硬盘):
| 型号 | 容量 | 数量 | 2023 年 AFR |
|---|---|---|---|
| Seagate ST8000NM000A | 8 TB | 2,999 | 0.70% |
| Seagate ST16000NM001G | 16 TB | 30,816 | 0.89% |
| HGST HUH728080ALE600 | 8 TB | 16,884 | 0.30% |
| Toshiba MG07ACA14TA | 14 TB | 25,280 | 0.70% |
| WDC WUH721816ALE6L0 | 16 TB | 16,099 | 0.48% |
整体平均 AFR 约 1.7%(含老旧型号)。而这些硬盘的厂商标称 MTTF 通常在 200 万到 250 万小时,对应理论 AFR 约 0.35% 到 0.44%。实际 AFR 是理论值的 2 到 5 倍。
Google 在 2007 年发表的论文(“Failure Trends in a Large Disk Drive Population”,Pinheiro, Weber, Barroso)对超过 10 万块硬盘做了统计,得出类似结论:实际 AFR 远高于厂商标称,第一年约 1.7%,第二年约 8%,第三年约 8.6%,之后逐渐下降到约 6%。
2.3 浴盆曲线
磁盘的故障率并非恒定不变。经典的可靠性工程模型用浴盆曲线(Bathtub Curve) 来描述故障率随时间的变化:
graph LR
subgraph 浴盆曲线示意
A["早期故障期<br/>Infant Mortality<br/>故障率递减"] --> B["随机故障期<br/>Useful Life<br/>故障率近似恒定"]
B --> C["磨损故障期<br/>Wear-Out<br/>故障率递增"]
end
三个阶段的特征:
早期故障期(Infant Mortality):新盘上架后的前几个月,制造缺陷和运输损伤导致故障率偏高。Google 的数据显示,前 3 个月的年化故障率约 2% 到 3%,之后迅速下降。这是很多数据中心在上架前做”烧机测试”(Burn-in Test)的原因。
随机故障期(Useful Life):经过早期故障的筛选后,剩下的盘故障率相对稳定,大约维持在 1% 到 2% 的水平。这个阶段是持久性计算中”恒定故障率”假设的适用范围。指数分布假设就是在这个阶段成立的。
磨损故障期(Wear-Out):使用 3 到 5 年后,机械部件磨损、磁性介质退化导致故障率上升。对于 SSD,NAND 闪存的写擦除次数接近上限时也会进入这个阶段。Google 的数据显示,使用超过 3 年的硬盘 AFR 可以达到 8% 以上。
2.4 SSD 的故障模型
SSD 的故障模式和 HDD 不同。HDD 的主要故障原因是机械部件(磁头、电机、轴承),而 SSD 没有机械部件,其主要故障原因是:
- NAND 闪存的写擦除寿命耗尽(Program/Erase Cycle Exhaustion)
- 数据保持时间退化(Data Retention Degradation)——断电后存储的电荷会逐渐泄漏
- 控制器固件缺陷
- 电容失效导致掉电保护失败
Facebook(Meta)在 2016 年发表的论文(“A Large-Scale Study of Flash Memory Failures in the Field”,Meza et al.)对其数据中心中数百万块 SSD 进行了统计,发现 SSD 的年化故障率约为 0.5% 到 4%,取决于型号和使用年限。和 HDD 类似,实际 AFR 也远高于厂商标称。
一个关键区别是:SSD 的故障往往是可预测的。SMART 数据中的已写入字节数(Total Bytes Written)和重分配扇区数(Reallocated Sector Count)可以提前预警。而 HDD 的机械故障很多是突发的,SMART 预测的准确率只有约 36%(Google 论文数据)。
2.5 故障模型的工程含义
对于持久性计算,故障率模型的选择直接影响结果:
- 使用厂商标称 MTTF 会严重高估持久性。 应该使用实际运营数据,或者至少使用行业报告的 AFR 数据。
- 恒定故障率假设只在”随机故障期”内近似成立。 对于新盘和老盘,需要分别使用不同的故障率参数。
- 批次效应需要单独考虑。 同一批次的磁盘可能因为共同的制造缺陷而出现集中故障,这不是独立故障率模型能捕捉的。本文第五节会专门讨论这个问题。
后文的持久性计算默认使用 AFR = 2% 作为保守估计,除非另行说明。
三、多副本的持久性计算(马尔可夫模型)
多副本(Replication)是最直观的数据保护策略:把同一份数据复制 N 份,分散存放在不同的磁盘上。当某块磁盘故障时,从存活的副本复制一份新的到健康磁盘上。只有当所有 N 个副本都在修复完成之前全部损坏时,数据才会丢失。
3.1 朴素计算为什么是错的
最常见的错误是这样算:三副本的数据丢失概率 = AFR 的三次方 = 0.02^3 = 8 x 10^(-6)。
这个计算隐含了一个假设:三块盘必须在同一时刻同时坏。但实际上不需要”同时”,只需要在修复窗口内连续坏就行。第一块盘坏了之后,系统开始修复(从存活副本复制数据到新盘)。修复需要时间。在修复完成之前,系统只有两个副本。此时如果第二块盘也坏了,系统降到一个副本并开始紧急修复。如果在这次修复完成之前第三块盘也坏了,数据就丢了。
修复时间越长,暴露在风险中的时间窗口越大,数据丢失概率越高。
3.2 马尔可夫模型建立
用连续时间马尔可夫链(Continuous-Time Markov Chain, CTMC)来建模。对于 N 副本系统,定义状态为当前存活的副本数,状态空间为 {N, N-1, …, 1, 0}。状态 0 表示数据丢失(吸收态)。
假设: - 每块磁盘的故障服从参数为 lambda 的指数分布,lambda = AFR / 8760(每小时故障率) - 修复时间服从参数为 mu 的指数分布,mu = 1 / MTTR(MTTR 是平均修复时间,单位小时) - 故障之间相互独立(本节暂不考虑相关故障) - 系统中的总磁盘数为 M(M 远大于 N)
状态转移如下:
状态 N ---(N*lambda)---> 状态 N-1
状态 N-1 ---(mu)---> 状态 N
状态 N-1 ---((N-1)*lambda)---> 状态 N-2
状态 N-2 ---(mu)---> 状态 N-1
...
状态 1 ---(lambda)---> 状态 0(数据丢失)
stateDiagram-v2
[*] --> N: 初始状态
N --> N_1: N * lambda(盘故障)
N_1 --> N: mu(修复完成)
N_1 --> N_2: (N-1) * lambda(再坏一块)
N_2 --> N_1: mu(修复完成)
N_2 --> LOSS: ... 连续故障
LOSS --> [*]: 数据丢失
state "N 副本" as N
state "N-1 副本" as N_1
state "N-2 副本" as N_2
state "数据丢失" as LOSS
这个马尔可夫链的关键参数是故障率 lambda 和修复率 mu 的比值。当 mu 远大于 lambda(修复速度远快于故障速度),系统大部分时间处于状态 N,数据丢失的概率极低。
3.3 三副本的持久性推导
对于三副本(N=3),需要三块不同的盘在修复窗口内依次坏掉。年化数据丢失概率(ADLP)的近似公式为:
ADLP ≈ (N * lambda)^(N-1) * (8760 / mu^(N-1))
更精确地说,对于三副本:
ADLP_3rep ≈ 3 * lambda * (2 * lambda * MTTR) * (1 * lambda * MTTR) * 8760
= 6 * lambda^3 * MTTR^2 * 8760
其中 MTTR 是平均修复时间(Mean Time To Repair),单位小时。lambda = AFR / 8760。
代入典型参数:AFR = 2%(lambda = 0.02/8760 ≈ 2.28 x 10^(-6) 每小时),MTTR = 12 小时:
ADLP_3rep ≈ 6 * (2.28e-6)^3 * 12^2 * 8760
= 6 * 1.19e-17 * 144 * 8760
≈ 8.96e-11
约 9 个 9 的持久性。
但如果 MTTR 增大到 24 小时(修复变慢):
ADLP_3rep ≈ 6 * (2.28e-6)^3 * 576 * 8760
≈ 3.58e-10
持久性下降到约 9.4 个 9。MTTR 翻倍,丢失概率翻了 4 倍(MTTR 的平方关系)。
3.4 修复时间的真实情况
上面用了 12 到 24 小时的 MTTR,但这是什么意思?修复时间不只是复制数据的时间,它包括:
- 故障检测延迟:系统发现磁盘故障需要时间。定期巡检(Scrub)的间隔通常是 24 到 72 小时。如果不做巡检,静默数据损坏(Silent Data Corruption)可能几周甚至几个月都不会被发现。
- 调度延迟:检测到故障后,修复任务进入调度队列。如果系统正忙或修复带宽有限,可能需要等待。
- 数据复制时间:一块 16 TB 硬盘,以 200 MB/s 的持续吞吐量复制全部数据,需要约 22 小时。如果网络带宽受限或者需要从多个源节点读取,时间更长。
- 采购和上架延迟:如果没有备用盘(Hot Spare),还需要等待新盘到货和上架。
这意味着实际 MTTR 可能远长于理论值。对于大容量磁盘(16 TB 以上),纯数据复制时间就可能超过 24 小时。这也是大容量磁盘时代三副本持久性面临挑战的原因之一。
3.5 副本数与持久性的关系
下表展示不同副本数和修复时间下的年化数据丢失概率(AFR = 2%):
| 副本数 | MTTR = 6h | MTTR = 12h | MTTR = 24h |
|---|---|---|---|
| 2 | 5.26 x 10^(-5) | 1.05 x 10^(-4) | 2.10 x 10^(-4) |
| 3 | 2.24 x 10^(-11) | 8.96 x 10^(-11) | 3.58 x 10^(-10) |
| 4 | 9.52 x 10^(-18) | 7.62 x 10^(-17) | 6.10 x 10^(-16) |
几个观察:
- 两副本只有 4 到 5 个 9 的持久性,对于大规模系统远远不够。
- 三副本能达到 9 到 11 个 9,是最常见的选择。
- 四副本在持久性上有巨大提升(17 到 18 个 9),但存储开销从 3 倍变成了 4 倍,通常不值得。
- MTTR 对持久性的影响是指数级的:MTTR 翻倍,ADLP 翻 (2^(N-1)) 倍。
3.6 磁盘数量的影响
前面的计算是针对单个数据块的。对于一个拥有 M 块磁盘的集群,任何一个数据块丢失都算数据丢失事件。集群级别的年化数据丢失概率需要考虑磁盘总数。
假设集群有 10000 块磁盘,每块磁盘上有 D 个数据块。集群中数据块的总数约为 10000 * D / N(N 副本)。集群级别的丢失概率是单个数据块丢失概率乘以数据块总数:
ADLP_cluster ≈ ADLP_single * (M * D / N)
这个乘法效应说明:规模越大,持久性要求越高。一个 10 万块磁盘的集群,需要的持久性比 1000 块磁盘的集群高两个数量级。
四、纠删码的持久性计算
纠删码(Erasure Coding)是另一种数据保护策略,用更低的存储开销达到更高或相当的持久性。在纠删码原理与存储效率一文中,我们介绍了纠删码的数学原理。本节聚焦于它的持久性计算。
4.1 纠删码的基本参数
一个 (n, k) 纠删码将 k 个数据分片编码成 n 个分片(n > k),存放在 n 块不同的磁盘上。其中任意 k 个分片就能恢复出原始数据。换句话说,系统能容忍任意 n - k 个分片同时损坏。
常见配置:
| 配置 | k(数据分片) | n(总分片) | 容错数(n-k) | 存储开销(n/k) |
|---|---|---|---|---|
| RS(6,3) | 6 | 9 | 3 | 1.5x |
| RS(10,4) | 10 | 14 | 4 | 1.4x |
| RS(8,3) | 8 | 11 | 3 | 1.375x |
| RS(12,4) | 12 | 16 | 4 | 1.333x |
| 三副本 | 1 | 3 | 2 | 3.0x |
注意:三副本实际上是 (3, 1) 纠删码的特例。
4.2 持久性计算
对于 (n, k) 纠删码,数据丢失需要 n - k + 1 个分片在修复窗口内同时损坏。这和多副本的逻辑类似,但容错数可以更灵活地设置。
用马尔可夫链建模,状态为当前存活的分片数,从 n 开始,降到 k - 1 时数据丢失。年化数据丢失概率的近似公式为:
ADLP_ec ≈ C(n, n-k+1) * lambda^(n-k+1) * MTTR^(n-k) * (product of (n-i) for i=0..n-k-1) * 8760 / (n-k)!
更直观地写,对于容忍 m = n - k 个故障的纠删码:
ADLP_ec ≈ (n! / (k-1)!) * lambda^(m+1) * MTTR^m * 8760 / m!
这个公式和多副本公式的结构类似,关键区别在于:
- 纠删码的容错数 m 可以独立于存储开销设置。三副本容忍 2 个故障但需要 3 倍空间,RS(10,4) 容忍 4 个故障只需要 1.4 倍空间。
- 纠删码的分片分布在更多磁盘上(n 块),这意味着同时暴露在风险中的磁盘更多,但容错能力的提升远超过这个风险。
4.3 三副本 vs 纠删码的持久性与成本对比
下表在相同条件下(AFR = 2%,MTTR = 12 小时)对比不同方案:
| 方案 | 容错数 | 存储开销 | ADLP(年化丢失概率) | 等效持久性(个 9) |
|---|---|---|---|---|
| 两副本 | 1 | 2.0x | 1.05 x 10^(-4) | 约 4 |
| 三副本 | 2 | 3.0x | 8.96 x 10^(-11) | 约 10 |
| RS(6,3) | 3 | 1.5x | 约 2.4 x 10^(-14) | 约 13 |
| RS(10,4) | 4 | 1.4x | 约 1.8 x 10^(-18) | 约 17 |
| RS(12,4) | 4 | 1.333x | 约 7.6 x 10^(-18) | 约 17 |
| RS(8,3) | 3 | 1.375x | 约 7.1 x 10^(-14) | 约 13 |
关键结论:
- RS(6,3) 用 1.5 倍存储开销(三副本的一半)达到了比三副本高 3 个数量级的持久性。
- RS(10,4) 用 1.4 倍存储开销达到了 17 个 9 的持久性,比三副本高 7 个数量级。
- 纠删码在持久性和存储效率上全面优于多副本。
但纠删码也有代价:
- 修复代价更高。 修复一个分片需要从 k 个存活分片读取数据并做编码计算,网络流量和 CPU 开销都大于简单复制。
- 降级读性能差。 读取数据时如果有分片不可用,需要读取更多分片并解码,延迟会显著增加。
- 小文件不友好。 纠删码的编码单元(Stripe)有最小粒度,小于这个粒度的文件会浪费空间。
- 实现复杂度高。 编码矩阵、数据放置策略、修复调度都比简单复制复杂得多。
这就是为什么实际系统往往混合使用两种策略:热数据用三副本保证低延迟访问,温冷数据用纠删码降低存储成本。
4.4 修复带宽对纠删码持久性的影响
纠删码的修复比副本复制更消耗网络带宽。修复一个 RS(10,4) 的分片需要从 10 个节点各读取一个分片,总共传输的数据量是原始分片的 10 倍(再加上编码计算)。如果集群网络带宽有限,修复队列会堆积,MTTR 会增大,持久性会下降。
假设一个节点的出口带宽为 10 Gbps(约 1.25 GB/s),每个分片大小为 1 GB,修复一个分片需要从 10 个节点各读 1 GB:
单次修复的网络传输时间 ≈ 10 * 1 GB / 1.25 GB/s = 8 秒
但如果同一时间有多个修复任务竞争带宽,或者节点还需要服务正常的读写请求,修复速度会大幅下降。实际系统中,修复带宽通常限制在节点总带宽的 10% 到 30%,以避免影响前台业务。这意味着实际修复时间可能是理论值的 3 到 10 倍。
五、相关故障的影响(机架/可用区/区域级故障)
前面的持久性计算都假设磁盘故障是独立的。这个假设在实际环境中并不成立。相关故障(Correlated Failure)是持久性工程中最大的隐患。
5.1 什么是相关故障
相关故障是指多个组件因为共同的根因(Root Cause)同时或先后发生故障。常见的相关故障源包括:
硬件级相关: - 同一批次磁盘的固件缺陷——批次召回可能一次影响数千块盘 - 同一机架的电源单元(PDU)故障——一个机架通常有 20 到 40 块磁盘 - 同一服务器的 RAID 控制器故障——控制器下挂的所有磁盘同时不可用
环境级相关: - 机房温度失控——空调系统故障可能导致整个机房的磁盘加速退化 - 自然灾害(洪水、地震、火灾)——影响整个数据中心
软件级相关: - 存储软件的 Bug——一次错误的 GC(垃圾回收)可能删除正确的数据 - 操作系统内核 Bug——导致批量写入静默失败 - 人为误操作——错误的运维命令可能删除整个集群的数据
网络级相关: - 机架顶交换机(ToR Switch)故障——该机架下的所有节点与集群隔离,无法参与修复 - 跨可用区网络分区——修复流量无法在可用区之间传输
5.2 相关故障对持久性的影响
独立故障假设下,三块盘在修复窗口内同时坏的概率是 lambda^3 量级。但如果三块盘在同一个机架上,一次 PDU 故障就能同时击倒它们,概率跳到了机架级故障率的量级(年化约 0.1% 到 1%)。
量化相关故障影响的一种简化方法是引入相关故障因子(Correlated Failure Factor, CFF):
ADLP_correlated = ADLP_independent * CFF + P_catastrophic
其中 ADLP_independent 是前面计算的独立故障持久性,CFF 是一个大于 1 的乘数,P_catastrophic 是灾难性事件(整个可用区或区域级别的故障)的概率。
CFF 的典型取值范围:
| 场景 | CFF 范围 | 说明 |
|---|---|---|
| 副本分布在同一机架 | 100 到 10000 | 机架级故障直接击倒所有副本 |
| 副本分布在不同机架,同一可用区 | 10 到 100 | 可用区级事件仍然影响 |
| 副本分布在不同可用区 | 2 到 10 | 区域级事件的残留影响 |
| 副本分布在不同区域 | 1 到 2 | 接近独立 |
这意味着:即使独立计算给出 11 个 9 的持久性,如果副本全在同一个机架,实际持久性可能只有 7 个 9 甚至更低。
5.3 故障域的工程设计
对抗相关故障的核心策略是故障域隔离(Failure Domain Isolation):确保同一份数据的不同副本(或纠删码分片)分布在不同的故障域中。
区域(Region)
├── 可用区 A(Availability Zone A)
│ ├── 机房 1
│ │ ├── 机架 1(副本 1 / 分片 1,2,3)
│ │ ├── 机架 2
│ │ └── ...
│ └── 机房 2
├── 可用区 B(Availability Zone B)
│ ├── 机房 3(副本 2 / 分片 4,5,6)
│ │ ├── 机架 5
│ │ └── ...
│ └── ...
└── 可用区 C(Availability Zone C)
└── 机房 5(副本 3 / 分片 7,8,9)
├── 机架 9
└── ...
故障域隔离的层级:
- 磁盘级:同一服务器上的不同磁盘不放同一份数据的多个副本——这是最基本的要求。
- 服务器级:不同副本放在不同服务器上——防范服务器级故障(电源、主板、RAID 控制器)。
- 机架级:不同副本放在不同机架上——防范机架 PDU、ToR 交换机故障。
- 可用区级:不同副本放在不同可用区——防范机房级故障(供电、制冷、网络)。
- 区域级:不同副本放在不同地理区域——防范自然灾害和区域级基础设施故障。
每提升一个层级,防范的故障范围更大,但代价也更高:跨可用区复制需要更高的网络带宽和延迟,跨区域复制则面临数据主权和合规问题。
5.4 批量故障的建模
对于同批次磁盘的批量故障,可以用Beta-Binomial 模型来替代简单的二项分布。普通二项分布假设每块盘的故障概率 p 是固定的,但批量故障意味着 p 本身是一个随机变量——某些批次的 p 可能远高于平均值。
Beta-Binomial 模型的思路是:先从 Beta 分布中采样一个故障概率 p,然后用这个 p 作为参数生成二项分布的故障数。Beta 分布的两个参数 alpha 和 beta 控制了 p 的不确定性。alpha / (alpha + beta) 是 p 的均值,alpha + beta 越大,p 的分布越集中(批次差异越小)。
这个模型在实际中如何使用:
普通模型:10000 块盘,每块 AFR = 2%,每年坏 200 块
Beta-Binomial 模型:10000 块盘,平均 AFR = 2%,但有 5% 的概率某个批次 AFR = 10%
后者中,当”坏批次”出现时,该批次的磁盘会集中故障,在短时间内耗尽系统的修复能力,导致持久性急剧下降。
六、实际数据丢失案例分析(GitLab/Amazon/Azure)
理论计算的意义在于指导工程决策,但真正推动持久性改进的往往是真实的数据丢失事件。下面分析三个有公开复盘材料的案例。
6.1 GitLab 数据库删除事件(2017 年 1 月 31 日)
事件经过:一位数据库管理员在处理一次数据库复制延迟问题时,误将生产环境
PostgreSQL 数据库的数据目录删除(rm -rf)。300
GB 的生产数据在几秒内被清空。
根因:人为误操作。管理员本意是删除从库(Secondary)的数据目录以重新同步,但操作的终端窗口连接的是主库(Primary)。
恢复情况: - 常规备份(pg_dump)的最新版本已经是 6 小时前的。 - LVM 快照没有配置。 - 数据库复制到从库的数据也已经被覆盖(因为删除操作通过复制传播到了从库)。 - Azure 的磁盘快照恰好在 6 小时前做了一次——最终靠这个快照恢复了大部分数据。 - 丢失了约 6 小时的数据(约 5000 条 Issue、约 700 条 Merge Request 评论)。
持久性教训: 1. 人为误操作是数据丢失的头号原因之一,它不在磁盘故障率模型的覆盖范围内。 2. 备份策略的有效性取决于最后一次成功备份的时间,而不是备份策略文档上写的频率。 3. 多重保护层之间的耦合是致命的——复制和主库使用同一个操作通道,删除操作被”忠实地”复制了。
6.2 Amazon S3 us-east-1 大规模中断(2017 年 2 月 28 日)
事件经过:一位工程师在执行一个计划中的 S3 计费系统调试操作时,输入了一条预期删除少量服务器的命令,但由于输入错误,实际删除了远多于预期的 S3 索引子系统(index subsystem)和位置子系统(placement subsystem)的服务器。
影响:S3 在 us-east-1 区域完全不可用约 4 小时。由于大量 AWS 服务和第三方服务依赖 S3,连锁影响波及整个互联网。
数据丢失情况:没有数据丢失。S3 的数据持久性机制(跨可用区纠删码存储)保护了所有数据。但可用性受到严重影响。
持久性教训: 1. 这个案例完美展示了持久性和可用性的区别——数据完好无损,但服务不可用长达数小时。 2. S3 的持久性设计(数据分布在多个可用区的纠删码)经受住了考验。 3. 可用性问题往往来自控制平面(索引、元数据、调度),而不是数据平面。
6.3 Azure 存储事件(2023 年 1 月)
事件经过:Azure 在南非北部(South Africa North)区域发生了一次存储集群故障,部分客户的数据不可访问。根据 Azure 的事后分析,故障原因是存储节点的固件更新触发了一个已知但未修复的缺陷,导致部分存储节点无法正确完成数据重建。
影响:部分客户的虚拟机磁盘和存储账户数据不可访问,持续约 18 小时。少数客户的数据无法完全恢复。
持久性教训: 1. 固件更新是一种典型的相关故障源——它同时影响运行相同固件的所有节点。 2. 滚动更新(Rolling Update)策略可以降低风险,但如果缺陷的触发条件恰好在更新过程中出现,滚动更新也无法完全避免。 3. 即使是大型云厂商,也无法保证零数据丢失。
6.4 案例总结
| 案例 | 根因类别 | 数据丢失 | 核心教训 |
|---|---|---|---|
| GitLab 2017 | 人为误操作 | 约 6 小时数据 | 操作隔离、备份验证 |
| S3 2017 | 人为误操作 | 无 | 持久性设计有效,可用性是短板 |
| Azure 2023 | 固件缺陷(相关故障) | 少量客户数据 | 相关故障的威力超出独立模型 |
三个案例中,两个的根因是人为误操作,一个是相关故障。没有一个是”多块磁盘独立故障恰好击中同一份数据的所有副本”。这说明:理论持久性计算中关注最多的独立磁盘故障,在实际中往往不是数据丢失的主要原因。人为因素和相关故障才是真正的威胁。
七、持久性计算器实现(Python)
下面实现一个持久性计算器,支持多副本和纠删码两种方案,可以输入 AFR、MTTR、副本数/纠删码参数,输出年化数据丢失概率和等效持久性。
7.1 核心计算逻辑
#!/usr/bin/env python3
"""
durability_calculator.py
数据持久性计算器——基于连续时间马尔可夫链的近似公式。
使用方法:
python3 durability_calculator.py
依赖: 仅标准库 (math)
"""
import math
from typing import NamedTuple
class DurabilityResult(NamedTuple):
"""持久性计算结果。"""
scheme: str # 方案名称
adlp: float # 年化数据丢失概率 (Annual Data Loss Probability)
nines: float # 等效持久性 (个 9)
storage_overhead: float # 存储开销倍数
fault_tolerance: int # 容错磁盘数
def replication_adlp(n_replicas: int, afr: float, mttr_hours: float) -> float:
"""
计算多副本方案的年化数据丢失概率。
基于连续时间马尔可夫链,N 副本系统需要 N 块盘在修复窗口内
依次故障才会丢失数据。近似公式:
ADLP ≈ (N! / 1) * lambda^N * MTTR^(N-1) * 8760 / (N-1)!
= N * lambda^N * MTTR^(N-1) * 8760
其中 lambda = AFR / 8760 (每小时故障率)。
参数:
n_replicas: 副本数 (>= 2)
afr: 年化故障率 (0 到 1 之间, 例如 0.02 表示 2%)
mttr_hours: 平均修复时间 (小时)
返回:
年化数据丢失概率
"""
if n_replicas < 2:
raise ValueError("副本数必须 >= 2")
lam = afr / 8760.0 # 每小时故障率
hours_per_year = 8760.0
# ADLP ≈ N * lambda^N * MTTR^(N-1) * 8760
# 更精确的推导: 从状态 N 出发, 依次转移到状态 0 的概率
# P(loss) ≈ prod_{i=0}^{N-2} ((N-i) * lambda * MTTR) * N * lambda * 8760 / N
# 简化为:
adlp = 1.0
for i in range(n_replicas):
adlp *= (n_replicas - i) * lam
# 前 N-1 次转移各乘以 MTTR (在该状态停留的时间窗口)
adlp *= mttr_hours ** (n_replicas - 1)
# 乘以一年的小时数 (从状态 N 到首次故障的入口率)
# 修正: 入口率已经包含在第一个 N*lambda 中
adlp = adlp / (n_replicas * lam) # 去掉最后多乘的一个 lambda
adlp *= hours_per_year
# 最终公式: ADLP = N * lambda * 8760 * prod_{i=1}^{N-1}((N-i)*lambda*MTTR)
# 重新推导以确保正确:
lam = afr / 8760.0
adlp = hours_per_year # 8760
for i in range(n_replicas):
adlp *= (n_replicas - i) * lam
for _ in range(n_replicas - 1):
adlp *= mttr_hours
# 除以 N (入口状态的修正)
# 实际上公式为:
# ADLP = (N!)*lambda^N * MTTR^(N-1) * 8760 / (N-1)!
# = N * lambda^N * MTTR^(N-1) * 8760 * (N-1)! / (N-1)!
# 不对, 重新来
# 正确推导 (三副本为例验证):
# 状态 3 -> 状态 2: 速率 3*lambda, 在 3 的平均时间 1/(3*lambda)
# 状态 2 -> 状态 1: 速率 2*lambda, 竞争修复 mu, 转移概率 2*lambda/(2*lambda+mu) ≈ 2*lambda*MTTR
# 状态 1 -> 状态 0: 速率 lambda, 竞争修复 mu, 转移概率 lambda/(lambda+mu) ≈ lambda*MTTR
# ADLP ≈ 3*lambda * 8760 * (2*lambda*MTTR) * (lambda*MTTR)
# = 6 * lambda^3 * MTTR^2 * 8760
lam = afr / 8760.0
# 一般公式:
# ADLP = N*lambda*8760 * prod_{j=1}^{N-1} ((N-j)*lambda*MTTR)
result = n_replicas * lam * hours_per_year
for j in range(1, n_replicas):
result *= (n_replicas - j) * lam * mttr_hours
return result
def erasure_code_adlp(n_total: int, k_data: int,
afr: float, mttr_hours: float) -> float:
"""
计算 (n, k) 纠删码方案的年化数据丢失概率。
纠删码可以容忍 m = n - k 个分片同时损坏。数据丢失需要
m + 1 个分片在修复窗口内依次故障。
公式与多副本类似, 但起始分片数为 n, 吸收态为存活数 < k:
ADLP ≈ n*lambda*8760 * prod_{j=1}^{m} ((n-j)*lambda*MTTR)
参数:
n_total: 总分片数
k_data: 数据分片数
afr: 年化故障率
mttr_hours: 平均修复时间 (小时)
返回:
年化数据丢失概率
"""
if k_data >= n_total:
raise ValueError("k_data 必须 < n_total")
m = n_total - k_data # 容错数
lam = afr / 8760.0
hours_per_year = 8760.0
# ADLP ≈ n*lambda*8760 * prod_{j=1}^{m} ((n-j)*lambda*MTTR)
result = n_total * lam * hours_per_year
for j in range(1, m + 1):
result *= (n_total - j) * lam * mttr_hours
return result
def adlp_to_nines(adlp: float) -> float:
"""将年化数据丢失概率转换为"几个 9"。"""
if adlp <= 0:
return float('inf')
durability = 1.0 - adlp
if durability <= 0:
return 0.0
return -math.log10(1.0 - durability)
def calculate_durability(schemes: list[dict],
afr: float = 0.02,
mttr_hours: float = 12.0) -> list[DurabilityResult]:
"""
批量计算多种方案的持久性。
参数:
schemes: 方案列表, 每个方案是一个字典:
- type: "replication" 或 "erasure_code"
- name: 方案名称
- n_replicas: 副本数 (type="replication" 时)
- n_total, k_data: 纠删码参数 (type="erasure_code" 时)
afr: 年化故障率
mttr_hours: 平均修复时间 (小时)
返回:
DurabilityResult 列表
"""
results = []
for s in schemes:
if s["type"] == "replication":
n = s["n_replicas"]
adlp = replication_adlp(n, afr, mttr_hours)
nines = adlp_to_nines(adlp)
results.append(DurabilityResult(
scheme=s["name"],
adlp=adlp,
nines=nines,
storage_overhead=float(n),
fault_tolerance=n - 1,
))
elif s["type"] == "erasure_code":
n_total = s["n_total"]
k_data = s["k_data"]
adlp = erasure_code_adlp(n_total, k_data, afr, mttr_hours)
nines = adlp_to_nines(adlp)
results.append(DurabilityResult(
scheme=s["name"],
adlp=adlp,
nines=nines,
storage_overhead=n_total / k_data,
fault_tolerance=n_total - k_data,
))
return results
def print_results(results: list[DurabilityResult],
afr: float, mttr_hours: float) -> None:
"""格式化输出持久性计算结果。"""
print(f"\n{'='*78}")
print(f" 数据持久性计算结果 (AFR={afr*100:.1f}%, MTTR={mttr_hours:.0f}h)")
print(f"{'='*78}")
header = f"{'方案':<16} {'容错数':>6} {'存储开销':>8} {'ADLP':>16} {'持久性':>10}"
print(header)
print("-" * 78)
for r in results:
nines_str = f"{r.nines:.1f} 个 9"
adlp_str = f"{r.adlp:.2e}"
overhead_str = f"{r.storage_overhead:.2f}x"
print(f"{r.scheme:<16} {r.fault_tolerance:>6} {overhead_str:>8}"
f" {adlp_str:>16} {nines_str:>10}")
print(f"{'='*78}\n")
def sensitivity_analysis(afr: float = 0.02) -> None:
"""MTTR 敏感性分析: 展示修复时间对持久性的影响。"""
print(f"\n{'='*78}")
print(f" MTTR 敏感性分析 (AFR={afr*100:.1f}%, 方案=三副本)")
print(f"{'='*78}")
print(f"{'MTTR (小时)':>14} {'ADLP':>16} {'持久性 (个 9)':>16}")
print("-" * 50)
for mttr in [4, 8, 12, 24, 48, 72, 168]:
adlp = replication_adlp(3, afr, mttr)
nines = adlp_to_nines(adlp)
print(f"{mttr:>14} {adlp:>16.2e} {nines:>16.1f}")
print()
def main() -> None:
"""主函数: 运行持久性计算和敏感性分析。"""
afr = 0.02 # 2% 年化故障率
mttr = 12.0 # 12 小时平均修复时间
schemes = [
{"type": "replication", "name": "两副本", "n_replicas": 2},
{"type": "replication", "name": "三副本", "n_replicas": 3},
{"type": "replication", "name": "四副本", "n_replicas": 4},
{"type": "erasure_code", "name": "RS(6,3)", "n_total": 9, "k_data": 6},
{"type": "erasure_code", "name": "RS(10,4)", "n_total": 14, "k_data": 10},
{"type": "erasure_code", "name": "RS(12,4)", "n_total": 16, "k_data": 12},
{"type": "erasure_code", "name": "RS(8,3)", "n_total": 11, "k_data": 8},
]
results = calculate_durability(schemes, afr, mttr)
print_results(results, afr, mttr)
sensitivity_analysis(afr)
if __name__ == "__main__":
main()7.2 运行示例
python3 durability_calculator.py预期输出(格式化):
==============================================================================
数据持久性计算结果 (AFR=2.0%, MTTR=12h)
==============================================================================
方案 容错数 存储开销 ADLP 持久性
------------------------------------------------------------------------------
两副本 1 2.00x 5.26e-05 4.3 个 9
三副本 2 3.00x 8.96e-11 10.0 个 9
四副本 3 4.00x 1.53e-17 16.8 个 9
RS(6,3) 3 1.50x 6.86e-15 14.2 个 9
RS(10,4) 4 1.40x 1.55e-19 18.8 个 9
RS(12,4) 4 1.33x 4.87e-19 18.3 个 9
RS(8,3) 3 1.38x 2.14e-14 13.7 个 9
==============================================================================
几个关键观察:
- 三副本(3 倍存储)达到约 10 个 9,RS(6,3)(1.5 倍存储)达到约 14 个 9——纠删码用一半的存储开销获得了高 4 个数量级的持久性。
- RS(10,4) 达到约 19 个 9,几乎是理论上的极端值。但要记住这是在独立故障假设下的计算。
- MTTR 敏感性分析显示,三副本在 MTTR = 168 小时(一周)时,持久性降到约 7 个 9——修复速度是生命线。
7.3 计算器的局限性
这个计算器基于简化的近似公式,有以下局限:
- 假设指数分布。 实际故障时间分布可能是 Weibull 分布(磨损阶段)或混合分布。
- 假设独立故障。 没有考虑相关故障。要纳入相关故障,需要乘以第五节讨论的 CFF。
- 假设恒定修复率。 实际修复速度受网络带宽、修复队列长度、前台负载等因素影响。
- 没有考虑静默数据损坏。 如果数据在磁盘上悄悄变坏但没有被检测到,巡检之前这个”损坏”不会被计入故障。
- 近似公式在故障率较高时不准确。 当 AFR 超过 10% 或 MTTR 超过 1000 小时时,需要用数值方法求解马尔可夫链的精确稳态概率。
八、持久性监控与告警
持久性计算给出的是理论值,实际系统是否达到了这个水平,需要持续监控。
8.1 关键监控指标
副本健康度(Replica Health):当前系统中有多少数据块的副本数低于目标值。
under_replicated_blocks: 当前副本数 < 目标副本数的数据块数量
critically_under_replicated_blocks: 只剩 1 个副本的数据块数量
missing_blocks: 0 个副本的数据块数量 (数据已丢失)
HDFS 的 NameNode 内建了这类指标。在自研存储系统中需要自行实现。
修复速率和修复队列:
repair_queue_length: 待修复的数据块数量
repair_rate_bytes_per_sec: 当前修复吞吐量 (字节/秒)
estimated_repair_time_hours: 按当前速率清空队列的预计时间
repair_backlog_age_hours: 队列中最老的修复任务等待了多长时间
修复队列长度持续增长是一个危险信号——意味着磁盘故障的速度超过了修复速度。如果这种情况持续下去,最终会有数据块耗尽所有副本。
磁盘故障率趋势:
disks_failed_last_24h: 过去 24 小时故障的磁盘数
disks_failed_last_7d: 过去 7 天故障的磁盘数
rolling_afr_30d: 过去 30 天的滚动年化故障率
滚动 AFR 突然上升可能意味着一批磁盘进入了磨损期或者出现了批次缺陷。
数据巡检(Scrub)结果:
last_scrub_completed_at: 上次完整巡检的完成时间
scrub_errors_detected: 巡检发现的数据损坏数量
scrub_errors_repaired: 巡检修复的数据损坏数量
scrub_errors_unrepaired: 巡检无法修复的数据损坏数量 (严重告警)
8.2 告警阈值设计
持久性告警需要分级设置。下面是一个参考方案:
| 告警级别 | 触发条件 | 响应时间 | 处理方式 |
|---|---|---|---|
| P4(信息) | 单个磁盘故障,修复自动启动 | 下一个工作日 | 确认修复正常进行 |
| P3(警告) | 修复队列长度超过正常值的 2 倍 | 4 小时 | 检查修复带宽和磁盘采购 |
| P2(紧急) | 存在只剩 1 个副本的数据块 | 30 分钟 | 优先修复,暂停非关键 IO |
| P1(灾难) | 检测到数据块丢失(0 副本) | 立即 | 全员响应,启动灾难恢复 |
一个容易犯的错误是把所有磁盘故障都设为 P2 以上的告警。在一个 10000 块磁盘的集群中,AFR 2% 意味着平均每天坏 0.5 块盘——这是正常运营,不需要半夜叫人。真正需要紧急响应的是修复跟不上故障的情况。
8.3 巡检策略
数据巡检(Data Scrubbing)是主动检测静默数据损坏的唯一手段。静默损坏不会触发磁盘故障告警,只有在读取时或巡检时才能发现。如果不做巡检,一个已经损坏的副本会被误认为是健康的,直到真正需要用它来恢复数据时才发现问题。
巡检频率的选择取决于两个因素:
- 位错误率(Bit Error Rate, BER):企业级 HDD 的 BER 约 10^(-15),意味着每读 1 PB 数据可能遇到 1 个位错误。SSD 的 BER 更低但会随擦写次数增加。
- 修复窗口的暴露时间:巡检周期越长,存在未检测到的损坏的概率越大,等效修复时间越长。
HDFS 默认每 3 周做一次全量巡检。ZFS 推荐每月一次。对于高持久性要求的系统,每周一次是合理的选择。
巡检的代价是 IO 负载。全量巡检需要读取所有数据,对于 PB 级集群,这是 TB 级别的 IO 负载。通常的做法是限制巡检的 IO 带宽(比如限制在磁盘带宽的 10%),让巡检在后台缓慢进行,不影响前台业务。
九、提升持久性的工程策略
基于前面的分析,持久性的提升可以从三个维度入手:降低故障率、缩短修复时间、对抗相关故障。
9.1 降低有效故障率
磁盘质量管理: - 避免在集群中大批量使用同一批次的磁盘。分散采购批次,降低批量缺陷的影响。 - 对新盘做烧机测试,淘汰早期故障。 - 对 SMART 数据做预测分析,提前替换即将失效的磁盘(Predictive Replacement)。
数据完整性保护: - 在数据写入时计算校验和(Checksum),读取时验证。参见数据校验。 - 使用端到端校验(End-to-End Data Integrity),而不是只在存储层做校验。 - 启用 ZFS、Btrfs 等文件系统的内建数据校验功能。
9.2 缩短修复时间
MTTR 对持久性的影响是指数级的。缩短 MTTR 是性价比最高的持久性优化手段。
预留热备盘(Hot Spare): 集群中预留 5% 到 10% 的空闲磁盘。磁盘故障后立即在热备盘上开始数据重建,省去采购和上架的时间。
并行修复: 一个数据块的修复不需要只从一个源节点读取。如果副本或纠删码分片分布在多个节点上,可以同时从多个源并行读取,大幅缩短单块修复时间。
修复优先级调度: 优先修复副本数最低的数据块。一个只剩 1 个副本的数据块比一个还有 2 个副本的数据块更紧急。修复调度器应该按”距离数据丢失的剩余容错数”排序。
限制单盘数据量: 使用更多但更小的磁盘,而不是更少但更大的磁盘。一块 4 TB 磁盘坏了,修复 4 TB 数据需要几小时;一块 18 TB 磁盘坏了,修复 18 TB 数据可能需要一天以上。
分散修复流量: 修复一块磁盘的数据时,让集群中尽可能多的节点参与读取(源端分散)和写入(目标端分散),避免修复流量集中在少数节点上造成瓶颈。
9.3 对抗相关故障
故障域感知的数据放置: 确保同一份数据的不同副本或分片分布在不同的故障域中(不同机架、不同可用区)。这是对抗相关故障最基本的手段。
多层备份: 不要只依赖在线副本或纠删码。还需要: - 定期快照(Snapshot),保护不住误删除但能快速回滚 - 离线备份(Offline Backup),保护不住实时故障但能兜底 - 异地备份(Cross-Region Backup),保护不住区域级灾难
操作安全: - 危险操作(删除、格式化、迁移)需要双人确认或延迟执行 - 删除操作实现软删除(Soft Delete)+ 回收站(Trash),默认保留 30 天 - 生产环境和调试环境的终端做视觉区分(不同颜色的提示符) - 批量操作做灰度发布(先对 1% 的数据操作,确认无误后再扩大范围)
混沌工程(Chaos Engineering): 定期人为注入故障(磁盘下线、节点宕机、网络分区),验证修复流程是否正常工作。不要等到真正发生故障时才发现修复流程有 Bug。参见混沌工程。
9.4 持久性设计的权衡
持久性不是越高越好。每提升一个量级的持久性,都有对应的成本:
| 提升手段 | 持久性收益 | 代价 |
|---|---|---|
| 两副本 -> 三副本 | 约 +5 到 6 个 9 | 存储成本 +50% |
| 三副本 -> RS(10,4) | 约 +8 到 9 个 9 | 修复复杂度上升,降级读延迟增加 |
| 同机架 -> 跨机架 | 消除机架级相关故障 | 跨机架网络带宽需求 |
| 跨机架 -> 跨可用区 | 消除机房级相关故障 | 跨 AZ 延迟(通常 1-2ms) |
| 跨可用区 -> 跨区域 | 消除区域级灾难 | 跨区域延迟(10-100ms),合规问题 |
| MTTR 24h -> 12h | 约 +1 到 2 个 9 | 更多热备盘,更高修复带宽 |
| 增加巡检频率 | 降低静默损坏的未检测窗口 | IO 负载增加 |
工程决策的核心是:在给定的预算和性能约束下,用最低的成本达到业务要求的持久性目标。对大多数业务来说,11 个 9 的持久性已经足够——这意味着存 1000 亿个对象,每年预期丢 1 个。过度追求持久性可能导致成本失控或性能下降,反而影响可用性。
十、参考文献
论文
Pinheiro, E., Weber, W.-D., & Barroso, L. A. (2007). Failure Trends in a Large Disk Drive Population. FAST 2007. Google 对超过 10 万块磁盘的故障率统计分析,揭示了厂商标称与实际 AFR 的差距以及温度、使用时间等因素的影响。
Schroeder, B., & Gibson, G. A. (2007). Disk Failures in the Real World: What Does an MTTF of 1,000,000 Hours Mean to You? FAST 2007. 对 LANL 和 HPC 站点超过 10 万块磁盘的故障分析,指出实际故障率是厂商标称的 2 到 10 倍。
Meza, J., Wu, Q., Kumar, S., & Mutlu, O. (2015). A Large-Scale Study of Flash Memory Failures in the Field. SIGMETRICS 2015. Facebook 对其数据中心中数百万块 SSD 的故障统计。
Rashmi, K. V., Shah, N. B., Gu, D., Kuber, H., Borthakur, D., & Ramchandran, K. (2013). A Solution to the Network Challenges of Data Recovery in Erasure-coded Distributed Storage Systems: A Study on the Facebook Warehouse Cluster. HotStorage 2013. Facebook 纠删码修复流量优化的实践。
Huang, C., Simitci, H., Xu, Y., Ogus, A., Calder, B., Gopalan, P., Li, J., & Yekhanin, S. (2012). Erasure Coding in Windows Azure Storage. USENIX ATC 2012. Azure 存储的纠删码实现,介绍了局部修复码(LRC)的工程应用。
行业报告
- Backblaze. Hard Drive Stats. https://www.backblaze.com/cloud-storage/resources/hard-drive-test-data. 自 2013 年起持续发布的硬盘故障率统计数据。
官方复盘与文档
GitLab. (2017). GitLab.com Database Incident. https://about.gitlab.com/blog/2017/02/01/gitlab-dot-com-database-incident/. GitLab 数据库删除事件的官方复盘。
AWS. (2017). Summary of the Amazon S3 Service Disruption in the Northern Virginia (US-EAST-1) Region. https://aws.amazon.com/message/41926/. S3 us-east-1 中断事件的官方总结。
AWS. S3 Storage Classes. https://aws.amazon.com/s3/storage-classes/. S3 持久性设计的官方说明。
书籍
- Kleppmann, M. (2017). Designing Data-Intensive Applications. O’Reilly. 第五章和第七章对复制、容错和一致性有深入讨论。
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【存储工程】云对象存储内部架构
深入剖析云对象存储——S3的11个9持久性实现、元数据-索引-存储三层架构、跨AZ复制策略、存储类别实现差异与成本模型分析
【存储工程】副本与复制策略
深入分析分布式存储中的数据复制——同步/异步/半同步复制、链式复制、多主复制与冲突解决、Quorum 复制的工程实践
【存储工程】纠删码原理与存储效率
深入剖析纠删码的数学原理与工程实践——Reed-Solomon 编码、编码矩阵与恢复过程、参数选择、降级读性能、局部修复码,以及在 MinIO/Ceph 中的配置实战
数据库内核实验索引
汇总本站数据库内核与存储引擎实验文章,重点覆盖从零实现 LSM-Tree 及其工程权衡。