f4 — Facebook 把 90 天前的旧图片搬到一个省 40% 存储的仓库
是什么
f4 是 Facebook 2014 年上线的温数据 BLOB 存储——专门接住已经”凉了”的旧图片旧视频。日常类比:你家厨房分两层——天天要用的锅具放料理台(Haystack),换季才用的烤盘搬到柜顶(f4)。柜顶不要求伸手就拿到,但占的格子要尽量少。
f4 的核心招数:用 Reed-Solomon 纠删码替代复制。Haystack 给每张图片存 3.6 份(单 DC 3 副本 + 跨 DC 备份),f4 把这个比例砍到 2.1 份——同样的数据少占 42% 的硬盘。
对 2014 年 Facebook 的 65 PB BLOB 来说,省 42% 是数千万美元级别的硬盘账单。
为什么重要
不理解 f4,下面这些事都没法解释:
- 为什么现代对象存储(S3 Glacier / Azure Cool Tier / 阿里云归档)都按”访问频次”分层,越冷越便宜
- 为什么 HDFS 3.0 默认支持纠删码、Ceph 有 erasure pool、Backblaze 公开 17/3 RS 方案——都是 f4 这套思路的工业延续
- 为什么”3 副本”在 2010 年是金科玉律、2014 年开始被打破——存储贵、网络便宜,账算过来了
- 为什么很多人以为 RS 就是”省钱的复制”,其实它有非常具体的适用边界(写完不改、读放大可接受)
核心要点
f4 做对了三件事:
-
承认数据有温度。BLOB 创建当天访问量是峰值,90 天后掉到 1/100。Haystack 全量按峰值规格存,浪费。f4 把”90 天前 + 卷已封存”的搬走,热的归热、冷的归冷。
-
单 DC 内用 Reed-Solomon (10, 4)。每 10 个 1GB 数据块算出 4 个校验块,14 块分散到 14 个机架。任意 4 块同时挂还能恢复。复制系数从 3 降到 1.4。
-
跨 DC 用 XOR 而不是复制。DC-A 的一个 stripe 和 DC-B 的另一个 stripe 做 XOR,结果存到 DC-C。任一 DC 整挂能从另两个还原。跨 DC 复制系数从 2 降到 1.5。
两步乘起来:1.4 × 1.5 = 2.1x。原来 3.6x,省 42%。
实践案例
案例 1:Reed-Solomon 在直觉上是怎么省的
类比:“14 个证人记同一句话,丢 4 个还能拼回来。” 数学上叫”多项式过 n 个点唯一确定”。
原始数据:D1 D2 D3 ... D10 (10 块各 1 GB)编码后: D1 D2 ... D10 P1 P2 P3 P4 (加 4 块校验,共 14 GB)分散存: 机架1 机架2 ... 机架14任意 14 选 10 都能解码原始数据。所以单 DC 内同时挂 4 个机架仍能读。
为什么省:复制 3 份要 30 GB 才能容忍 2 副本挂;RS 用 14 GB 容忍 4 块挂。容忍度更高、占用更少。
案例 2:跨 DC 的 XOR 巧思
三地档案馆比喻——A 城存账本前半页,B 城存后半页,C 城只存”前半页 XOR 后半页”。任一城烧了:
- A 烧了:从 C 取 XOR、从 B 取后半页 → 前半页 = XOR ⊕ 后半页
- B 烧了:对称
- C 烧了:直接重算
所以只要不是同时烧两个城市,数据都还在。每个 stripe 在主 DC 占 1.4x(已经是 RS 编码后),跨 DC 再加 0.5x 的 XOR 块(因为两个 stripe 共用一份 XOR)。两步乘起来就是 1.4 × 1.5 = 2.1x 的总有效复制系数。
案例 3:什么时候搬家
f4 不是创建即用。流程:
- 新 BLOB 写到 Haystack(3 副本,低延迟)
- 卷写满 100 GB → 封存,从此只读
- 封存满 90 天 → 后台任务搬到 f4(编码成 RS stripe)
- 用户读旧图片时由 router 透明转发到 f4
90 天这个门槛是经验值——这时访问量已经掉两个数量级,多花几毫秒读延迟用户感知不到。
案例 4:f4 的内部部件分工
读这种系统论文很容易在术语里迷路,把每个组件想成一个具体角色就清楚了:
- name node:图书馆的目录卡片柜,告诉你”BLOB X 在第几个 stripe 第几块”,必须 3 副本因为 QPS 高
- data node:实际放硬盘的书架
- block server:管编码解码的工人,写时把 10 块算出 4 个校验,读时如果发现某块挂了就从其他 10 块重算
- rebuilder:定期巡逻,发现哪个机架挂了就启动重建
- coordinator:跨机架平衡器,避免某个机架承担过多 stripe 块
- router:前端请求路由,根据 BLOB ID 决定去 Haystack 还是 f4
一个 cell(独立故障域)= 14 机架 × 15 机器 × 30 盘 ≈ 数 PB。机架是”一损俱损”的最小单元,所以 RS 的 14 块严格分散到 14 个机架。
踩过的坑
-
RS 修盘成本是复制的 10 倍:丢 1 个 1GB 块,要从同 stripe 的 10 个其他块各读出来重算。复制方案修盘只读 1 GB,RS 要读 10 GB。所以温数据可以、热数据用 RS 会被修盘流量打爆。Microsoft Azure 后来用 LRC(局部重构码)改进这一点。
-
对小文件不友好:一个 stripe 14 GB,需要把很多小 BLOB 拼成一个逻辑卷凑齐。如果你的对象平均 1 KB,meta 开销比数据还大。
-
删除是标记位,不真删:物理回收要等 stripe 整体改写。频繁删除会留空洞,效率掉。
-
metadata 仍用复制:name node 高 QPS、低延迟,禁用 RS。架构上是”数据走 RS、索引走 3 副本”。
-
温热分层门槛错了用户会骂:90 天是工业经验,但热门事件相册可能 3 年还在被翻。用错门槛 = 用户翻 5 年前相册时觉得卡。
适用 vs 不适用场景
适用:
- 对象存储的冷/温层(S3 Glacier、Azure Cool Tier、HDFS 归档分区)
- 写完很少改、读频次随时间衰减的数据(图片、视频、日志归档、备份)
- 平均文件 ≥ 几 MB(能凑齐 stripe)
- 跨 DC 容灾但带宽/容量都贵
不适用:
- 热数据/低延迟读:RS 修盘放大读 → 用复制
- 频繁更新:每次改重算整 stripe → 用 LSM 或 B-tree
- 平均文件 < 1 MB:拼 stripe 难、metadata 占比高
- 单机或小集群:RS 跨节点收益小、复杂度不值
历史小故事(可跳过)
- 1960:Irving Reed 和 Gustave Solomon 提出 RS 编码,先用于深空通信和 CD 纠错
- 2003-2006:GFS / Bigtable 定下”3 副本”工业基线,简单粗暴但贵
- 2010:Facebook Haystack(OSDI 2010)解决小图片 metadata 爆炸;同年 HDFS-RAID 首次在工业级文件系统用 RS 做归档
- 2012:Microsoft Azure 论文公开 LRC——RS 的修盘改进版
- 2014:f4 这篇——把”分层”和”跨 DC XOR”都系统化
- 之后:Backblaze 公开 17/3 实现、Ceph 默认带 erasure pool、HDFS 3.0 把 RS 做进核心、对象存储普遍按温度计费
学到什么
- 数据有温度,存储就该分层。一刀切的 3 副本是把所有数据都按峰值规格存,浪费。
- 复制和纠删码不是替代关系,是不同温度档的工具。热的用复制(修盘便宜),温/冷的用 RS(占用便宜)。
- (n, k) 参数是工程权衡:(10, 4) 是 Facebook 的折中——块少则容忍度低、块多则修盘读更多。
- 跨地理冗余可以不靠完整复制:XOR 这种”一份顶半份”的思路省得多,前提是能接受多地协调的复杂度。
- 元数据和数据的存储策略可以拆开:高 QPS 索引仍用 3 副本,大体量数据用 RS。
延伸阅读
- 论文 PDF:Muralidhar et al., OSDI 2014(14 页,密度适中,工程图很清晰)
- Backblaze 公开实现:Backblaze Reed-Solomon(带可运行 Java 代码,新人入门首选)
- 视频:USENIX OSDI 2014 — f4(一作演讲 25 分钟)
- reed-solomon-1960 —— f4 用的数学基础
关联
- reed-solomon-1960 —— RS 编码诞生论文,f4 是它在 2014 年的工业落地
- gfs —— 3 副本工业基线,f4 直接挑战的对象
- hdfs-2010 —— 同时代 BLOB 存储另一条路线
- azure-storage-2011 —— 微软对象存储 + 后续 LRC 改进 RS
- dynamo —— 分布式存储的另一种设计哲学(最终一致性 vs f4 的强一致只读)
反向链接
- azure-storage-2011 —— Windows Azure Storage 2011 — 云对象存储第一次在工业界做到强一致
- dynamo —— Dynamo — 让购物车永远能写入的分布式存储
- gfs —— GFS — 编译器决定不做哪些事
- hdfs-2010 —— HDFS — 把 GFS 用 Java 重写一遍并撑到 25 PB
- reed-solomon-1960 —— Reed-Solomon 编码