Fan-Out 在社交媒体信息流中的第一原理解释
I’m happy to translate the article for you, but I need the full text of the post (the content you’d like translated). Could you please paste the article’s body here? Once I have it, I’ll provide the Simplified‑Chinese translation while keeping the source link, formatting, markdown, and any code blocks exactly as they are.
从最简单的问题开始
在讨论 fan‑out 之前,我们需要先明确我们实际要解决的是什么问题。
一个去掉品牌和算法的社交媒体信息流,归结为以下一点:
当有人创建一条帖子时,其他人应该能够看到它。
这就是全部功能。其他所有东西——点赞、排序、推荐、通知——都是因为这个简单的需求在大规模时会变得极其昂贵。
现在把这句话翻译成系统语言:
一次写入操作必须产生许多可读取的结果。
从 一次写入到多次读取 的这种转变,就是我们所说的 fan‑out。fan‑out 不是一个功能,而是一个结果。
第一性原理:现实对我们的约束
在选择设计之前,我们需要了解问题空间的形状。这些不是观点,而是人和计算机行为所强加的约束。
读取远多于写入
在任何社交系统中,用户的读取远多于写入。一个帖子可能被阅读数千、数百万,甚至上亿次,但它只会被写入一次。
这种不对称很重要。任何让读取变得昂贵的设计都会持续受到影响。任何让写入变得昂贵的设计则只会偶尔受到影响。在大规模时,系统几乎总是选择偶尔受损而不是持续受损。
社交图呈现极端不均衡
大多数用户的关注者数量很少。少数用户的关注者数量却极其庞大。这里没有平滑的曲线;而是一道悬崖。
这意味着“向关注者展示此帖”的成本通常很小,但有时会高得惊人。如果你的设计没有明确考虑到这一点,它在测试中可能表现完美,却在生产环境中崩溃。
延迟是一种用户情感,而非单纯指标
从人的角度来看,100 毫秒与300 毫秒的差别几乎感觉不到。300 毫秒与1 秒的差别则让人觉得系统已坏。
信息流必须感觉是即时的。这意味着信息流的读取必须 几乎总是 快速,而不是仅在平均水平上快。尾部延迟比平均延迟更重要。
存储比计算和网络跳数更便宜
磁盘很便宜。CPU 时间和网络调用则不然。每一次额外的查询、每一次额外的服务跳转、每一次额外的合并步骤都会体现在延迟上。
这促使真实系统倾向于预计算和缓存,即使这意味着要复制数据。
牢记这些约束。扇出(fan‑out)的存在正是因为它们。
最明显的解决方案(以及它为何失败)
构建信息流的最简单方式是等用户请求时再计算。这种做法通常称为 fan‑out on read(读取时展开)。
概念上的工作原理
- 当用户打开他们的动态时,系统会查找他们关注了哪些人。
- 从这些用户的最近帖子中获取数据。
- 将所有帖子合并、排序、排名,然后返回结果。
没有任何预先计算;所有操作都在请求时即时完成。
为什么大家都会从这里起步
- 看起来很干净。
- 避免了数据重复。
- 写入操作非常简单。
- 存储使用量低。
- 逻辑直接对应产品需求:“显示我关注的人的帖子”。
如果你在构建原型或早期产品,这种方式往往能够工作——因此很有诱惑力。
为什么它在大规模时会崩溃
- 读取信息流的成本会随关注人数线性增长,单个请求可能会展开为数十甚至数百个数据库查询。
- 缓存未命中会进一步放大问题。
- 排序逻辑会增加计算量。
- 网络延迟会叠加。
结果是延迟不可预测:有的请求很快,有的则异常缓慢。一旦真实流量到来,系统就会出现抖动。

这违背了可伸缩系统的基本原则:读取路径必须具有有界成本。读取时展开使得读取在用户最关心性能的时刻变得昂贵。
将问题反向
为了解决这个问题,工程师们把问题翻转了过来。不是在用户阅读时计算信息流,而是在用户写入时计算信息流。这种做法称为 fan‑out on write。
工作原理
当用户创建一条帖子时,系统会查找该用户的所有粉丝,并提前把该帖子的引用插入每个粉丝的信息流中。当粉丝随后打开信息流时,系统只需读取预先构建好的列表。
为什么效果这么好
- 信息流读取变成了简单的查找——无需合并、无需大量计算。
- 延迟变得可预测且低。
- 完全符合之前“读取占主导、写入较少”的约束:一次付出代价,获得多次收益。
如果所有用户的粉丝数量都相似,这就能完美收场。可惜,人类把一切都弄得复杂。
名人问题
写时 fan‑out 隐含了一个假设:每个用户的关注者数量是适度的。当拥有数百万关注者的用户发布内容时,系统必须在单个事务中写入数百万条记录,这可能会压垮存储、CPU 和网络资源。
文章的其余部分(未显示)探讨了处理这种“名人”边缘情况的策略,例如混合 fan‑out、惰性传播和分片技术。
Fan‑out on Write 的成本
对于大多数用户来说,写入时的 fan‑out 是可以接受的。
如果某人有 200 位粉丝,插入 200 条动态记录并不是什么大事。
但也有一些用户拥有 数百万 的粉丝。
对他们而言,一条帖子会触发数百万次写入——可能跨分片、复制并排队。即使是异步执行,这也会给系统带来巨大的压力。
规则: 任何单个请求都不应具有无限制的成本。
因此,纯粹的写时 fan‑out 仍然会以另一种方式失败。
真实系统收敛的解决方案
大型社交系统不会只选一种方法;它们 组合 它们。这被称为 混合 fan‑out 模型。
工作原理
| 用户类型 | Fan‑out 策略 |
|---|---|
| 可管理的粉丝数量 | 写时 fan‑out – 帖子直接推送到粉丝的时间线。 |
| 极大量的粉丝数量 | 读时 fan‑out – 帖子在用户阅读时拉取到时间线。 |

这在保持大多数用户快速读取的同时,限制了最坏情况的写入成本。
- 它优雅吗? 并不真的。
- 它能在真实流量下生存吗? 能。
正是这种权衡才重要。
重新思考 Feed 的本质
一个常见的误解:Feed 并不是查询。
Feed 是一个 物化视图——预先计算好的、按用户划分的内容引用(指针)索引,而不是内容本身。
典型的 Feed 条目字段:
user_idpost_idtimestampmetadata(用于排序)
帖子内容存放在其他位置。
这种分离让系统能够:
- 重建 Feed
- 重新排序内容
- 在删除时无需重写全部数据
把 Feed 视为缓存的索引,就把 fan‑out 转变为一个 维护问题,而不是计算问题。
事件驱动的 Fan‑out 是强制性的
永远不要同步执行 fan‑out。
- 在创建帖子时 触发一个事件。
- 后台工作者 消费该事件并 异步 更新信息流。
好处:
- 重试和背压处理
- 从故障中恢复
- 与社交媒体 最终一致 的现实保持一致

用户无法判断一条帖子是 200 毫秒后还是一秒后出现在他们的信息流中,而你的基础设施显然可以。
实际重要的思维方式
在设计信息流系统时,具体使用哪种数据库或队列技术 不如 你提出的问题重要:
- 最坏情况下的 fan‑out 大小 是多少?
- 请求的成本是否有上限?
- 当 工作者落后 时会发生什么?
- 信息流能否 重新构建?
- 删除 和 隐私变更 如何传播?
Fan‑out 并不是技巧的比拼,而是对约束的尊重。
实际适用的最终类比
- Fan‑out on read = 每次饿了就做饭。
- Fan‑out on write = 预先准备餐食。
烹饪听起来很灵活,直到你必须一次性为 数百万 人服务。
在大规模时,预先准备更胜一筹。
要点
Fan‑out 不是一种优化;它是将单个操作在极端倾斜的情况下转化为多个体验的不可避免的结果。
一旦你明白信息流是 预先计算的、针对每个用户的视图,并在持续压力下维护,社交媒体系统设计的其余部分就会开始让人觉得不舒服,却又合情合理。
而且,是的,从这里开始只会变得更难。