Jiayun's Blog

探索与分享

📌 好文共赏 | Editor’s Pick

原文:When “idle” isn’t idle: how a Linux kernel optimization became a QUIC bug 作者:Esteban Carisimo & Antonio Vicente(Cloudflare 系统工程团队)| 发布于:2026-05-12 | 阅读时长:约 12 分钟

多模评分:Opus 9.25 / Sonnet 9.0 / Gemini 9.3(综合 9.18 / 10

一句话推荐理由:这是 2026 年迄今为止我读过的、最接近"教科书"的一次工程根因分析——它把网络协议史、跨语言移植的时间债、最小 cwnd 状态机的脆弱、以及"测试为什么必须刻意去戳系统最难受的姿势"这四件事,用一个 999 次状态翻转的图表串成了一条线。

为什么值得读

如果你只把这篇文章看成"Cloudflare 修了个 QUIC bug",那就太可惜了。

这篇博客的真正价值在于:它一次性把网络协议工程里最容易被忽略的几条暗线全部点亮了

第一条暗线,是算法跨语言、跨子系统移植时的"时间债"。Linux 内核 CUBIC 在 2017 年提交了一个针对 application-limited 场景的补丁,一周后又打了一个 follow-up 修正——但 Cloudflare 在 2020 年把这段逻辑 port 到 user-space 的 Rust QUIC 库 quiche 时,只搬了第一个补丁,错过了那个 follow-up。这件事六年后变成了一个 60% 失败率的集成测试。这种"被 port 出来的孤儿代码"在每一个重写过的子系统里都隐藏着,只是大多数情况下没有压力测试把它逼到表面。

第二条暗线,是最小窗口(min-cwnd)下的状态机非常容易反直觉地稳定在错误状态上。绝大多数拥塞控制器的测试,都在测它稳态增长阶段的表现:能不能撑满带宽?带宽变化时反应快不快?但很少有人测它从崩溃中爬出来的过程。而这篇文章里 Carisimo 与 Vicente 一句话戳穿了这个习惯:

原文:Recovery after congestion collapse is an uncommon regime, but it is exactly the regime a congestion controller exists to handle.(来源:blog.cloudflare.com)

把这句话翻译成更普适的工程哲学就是:控制回路 99% 走的那条路是"用来日常运转的",但你真正需要它正确的,是那条出事时才会走的路。这一条几乎可以贴在所有 SRE 团队会议室的白板上。

第三条暗线,是协议工程的"路径依赖"和上下游的信息流断裂。在 HN 评论区,Google 前网络工程师 vasilvv 留了一条非常关键的注脚——他说这个完全相同的 CUBIC bug(quiescent 期之后 cwnd 暴涨)他 2015 年在 Google 内部 QUIC 实现里就遇到过,并把它反向汇报给了 Linux TCP 团队。换句话说,这个问题在三个互相独立的实现里被独立踩中过,每次都让作者重新发明了一遍轮子。

第四条暗线——也是这篇文章我个人最喜欢的部分——是优雅修复的代价是天量的可观察性投入。原文最后一句轻描淡写:

原文:As we noted during the investigation: the effort to find the bug was massive, but the fix itself was basically one line of logic.(来源:blog.cloudflare.com)

但你只要看一眼他们贴出来的 qlog 可视化图表——那张 999 次状态翻转、6.7 秒、每 14ms 一次的密集竖线——就知道为了"看见"这个 bug,他们在测试管线、qlog instrumentation、可视化工具上做了多少铺垫。

这篇文章和我之前写过的《Cloudflare “Code Orange” 实践全解析:如何用 18 个月将 P0 事故降低 73%》讨论的是同一种文化——把工程问题真正讲清楚的能力,本身就是 Cloudflare 的护城河之一,即使这个故事的具体调试是在 quiche 这个相对小众的 Rust 项目里发生的。

核心观点深度解读

1. CUBIC 的发动机比你想象得更精巧——也更容易"骑坏"

CUBIC(RFC 9438)是 Linux 自 2008 年左右默认的 TCP 拥塞控制算法,今天也是 QUIC 在多数实现里的默认选项。它和 Reno 这一类"乘性减、加性增"算法(AIMD)不同之处在于:它把窗口的增长曲线显式建模成一个关于"距上次拥塞事件时间"的三次函数

$$ W_{\text{cubic}}(\Delta t) = C \cdot (\Delta t - K)^3 + W_{\max} $$

其中 $W_{\max}$ 是上次丢包发生时的窗口,$K$ 是回到 $W_{\max}$ 所需的时间,$C$ 是一个常数。三次曲线意味着:刚丢包之后的几个 RTT 增长很慢(保护刚刚拥堵的链路),接近 $W_{\max}$ 时近乎水平(试探拐点),超过 $W_{\max}$ 后才开始三次方加速冲到下一个上限。

这个曲线的精巧之处在于:它把所有的内部状态压缩成了一个 epoch 时间戳 $t_{\text{epoch}}$。$\Delta t = \text{now} - t_{\text{epoch}}$。只要 epoch 不动,曲线就在自然地往前长。

而这就是整个故事的入口点。

原文:The epoch is the reference timestamp CUBIC uses to anchor its growth curve. … Between resets, delta_t grows monotonically with wall-clock time.(来源:blog.cloudflare.com)

epoch 在两种情况下会被"动":(1)丢包发生,重置;(2)应用空闲一段时间后恢复发送时,需要"补偿"地往前挪 $\Delta t_{\text{idle}}$。前者是教科书内容,后者就是这次 bug 的全部战场。

2. 2017 年那个 Linux 内核补丁,到底在补什么

要理解 quiche 2026 年这个 bug,必须先回到九年前那个看似无关的内核 commit。

问题是这样的:如果一个连接已经退出 slow-start 进入拥塞避免阶段,然后应用层突然不发数据了(比如视频流的关键帧间隙,比如 RPC 的等待响应),CUBIC 的 epoch 不会自动更新。等应用过几秒钟想再发数据时,$\Delta t = \text{now} - t_{\text{epoch}}$ 就被空闲时间膨胀得非常大,三次曲线代入这么大的 $\Delta t$,立刻算出一个夸张的目标窗口——cwnd 会被瞬间打到一个完全脱离物理网络当前状态的值。

原文:The delta “t” of now - epoch_start can be arbitrary large after app idle as well as the bic_target. Consequentially the slope … would be really large, and eventually ca->cnt would be lower-bounded in the end to 2 to have delayed-ACK slow-start behavior.(来源:blog.cloudflare.com 引用 Linux kernel commit)

Google 工程师 Jana Iyengar 最初的修法很直觉:把 epoch_start 重置到"现在"。但 Neal Cardwell 立刻指出这不对——这相当于让 CUBIC 误以为刚刚发生了一次丢包事件、要从零重新长曲线。更聪明的做法是把整条曲线沿时间轴平移:epoch 往后挪 $\Delta t_{\text{idle}}$,曲线的形状保持不变,只是"挪到 idle 之后再继续长"。

最终由 Eric Dumazet、Yuchung Cheng、Neal Cardwell 共同提交的版本就是这个 shift-the-epoch 方案。这是 2017 年 4 月。一周以后,第二个补丁进来了,因为 epoch 计算的位置(packet send 时)和它本该锚定的位置(ACK 处理时)之间有一个微妙的时序差异——内核维护者们决定不增加新的状态变量,只在 ACK 处理时简单地"不要把 epoch 设到未来"。

两个补丁,组成了 Linux CUBIC 关于 idle 的完整故事。而 quiche 2020 年只抄了第一个。

3. user-space 的世界没有 CA_EVENT_TX_START

Cloudflare 把 CUBIC 从内核 port 到 quiche 这个 Rust user-space QUIC 库时,遇到了一个具体的工程现实:内核里有 CA_EVENT_TX_START 这个 callback,每次新一轮发送开始时它会触发;但 user-space 没有这种钩子。

quiche 的工程师做了一个直觉上很合理的近似:on_packet_sent() 里看一眼 bytes_in_flight,如果是 0,就认为刚刚发生过 idle,然后把 congestion_recovery_start_time 往前推 $\text{now} - \text{last_sent_time}$。这段逻辑非常短:

1
2
3
4
5
6
7
8
// 简化伪代码
fn on_packet_sent(&mut self, bytes_in_flight: usize, now: Instant) {
    if bytes_in_flight == 0 {
        let delta = now - self.last_sent_time;
        self.congestion_recovery_start_time += delta;
    }
    self.last_sent_time = now;
}

这段代码在带宽富余、cwnd 很大的常态下完全没问题,因为应用真正 idle 的时候 bytes_in_flight 才会归零,now - last_sent_time 也确实近似 idle 时长。

但当 cwnd 被打到地板(两个 MTU 大小的包),整个连接变成严格的 stop-and-wait 时,所有假设都翻车了:

  • 服务端发出 2 个包后,进入"等 ACK"状态,bytes_in_flight = 2
  • 一个 RTT 之后(≈10ms 实验配置 + 一点点 jitter),两个 ACK 同时到达,处理时 bytes_in_flight 瞬间归零。
  • 应用层立刻又准备好下一波发送,于是 on_packet_sent() 触发。
  • 此时 bytes_in_flight == 0 成立——但它不代表 idle,它代表"被 cwnd 卡住"
  • delta = now - last_sent_time ≈ 14ms 被错误地当作 idle 时长,把 congestion_recovery_start_time 推到了未来
  • 紧接着的 ACK 处理 in_congestion_recovery() 返回 true,cwnd 跳过本轮增长,仍然是两个包。
  • 下一个 RTT,再来一遍。

每 RTT 翻转一次状态,连续 999 次,cwnd 永远卡在两个包,把"10 秒应该 5 秒下完"的测试逼到了 60% 失败率。这就是文章标题里那个"death spiral"——最小 cwnd 把 bug 的触发条件锁定成了稳态

4. 14ms:那个数字背后的"时钟节拍"

文章里 Carisimo 用了一个非常优雅的诊断动作——把 qlog 输出和包丢失事件画在同一条时间轴上,然后注意到状态翻转的周期是 ~14ms,几乎等于 RTT 10ms 加上调度抖动

原文:Whatever is triggering the recovery/avoidance flip is happening once per round trip, in lockstep with connection’s ACK clock.(来源:blog.cloudflare.com)

这是一个非常老派的网络人的洞察:当某个错误现象的周期等于 RTT 时,bug 几乎一定锁在 ACK clock 上——也就是说,连接的 self-clocking 节奏(每一轮 ACK 触发下一轮 send)正在把这个错误状态当成一个"信号"传递下去。

这跟我之前写过的《Rust 异步生态的分裂与重聚:io_uring、Tokio 单极、和一个迟到的标准》里讲过的"事件循环里隐式的时钟"是同一种 pattern:任何 event-driven 系统的稳定 bug,都会有一个隐式的时钟把它的相位固化下来。你要做的,是先识别出那个时钟,再问"什么事件踩在它上面"。

5. 修复:让 idle 从该结束的时刻算起

修复非常小,三行 Rust:加一个 last_ack_time 字段,ACK 到达时更新它,然后在 on_packet_sent 里把"idle 起点"改成 max(last_ack_time, last_sent_time)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// 修复后的逻辑骨架
if bytes_in_flight == 0 {
    if let Some(recovery_start) = r.congestion_recovery_start_time {
        let idle_start = cmp::max(cubic.last_ack_time, cubic.last_sent_time);
        if let Some(idle_start) = idle_start {
            if idle_start < now {
                let delta = now - idle_start;
                r.congestion_recovery_start_time = Some(recovery_start + delta);
            }
        }
    }
}

为什么这一改就对了?因为 last_ack_time 才是 bytes_in_flight 真正归零的时刻——在最小 cwnd 的死亡螺旋里,last_ack_time ≈ now,所以 delta ≈ 0congestion_recovery_start_time 几乎不动,下一次 ACK 处理就会发现"咦,我已经走出 recovery 了",正常进入增长。在真正 idle 的连接里,last_ack_time 远在过去,整体行为退化回原来 epoch-shift 的语义。

原文:For genuinely idle connections, last_ack_time is far in the past and the same expression captures the full idle duration, the original epoch-shift behavior is preserved.(来源:blog.cloudflare.com)

一个状态变量、一行核心逻辑、修复一个跨越六年的协议 port-over bug。这是工程美学的高光时刻——但请注意,这个美学只有在一个肯花几周时间做可观察性、肯写让算法在极端 regime 里出汗的测试、肯把内核 commit message 一行行读完的团队里才可能发生。

6. 这个 bug 的"考古学"——三个独立实现各自踩坑

HN 评论区里 Google 前 QUIC 工程师 vasilvv 的留言堪称这篇文章的"隐藏番外":

原文:This is somewhat funny to read because this specific issue in CUBIC (sudden CWND jump upon existing quiescence) was originally discovered in Google’s QUIC library and then later reported to the team working on the TCP stack. I know this because I was the one who found that bug back in 2015.(来源:news.ycombinator.com 评论区)

把时间线拉直就会发现一件非常黑色幽默的事:

  • 2015:Google 工程师在 Google QUIC 实现里发现 CUBIC quiescent-CWND-jump bug
  • 2017:同一个根因在 Linux 内核 CUBIC 里被发现并修补(两次 commit)
  • 2020:Cloudflare quiche 项目把内核 CUBIC port 到 Rust,漏抄第二个 commit
  • 2026:第二个 commit 漏抄的后果在 quiche 集成测试 60% 失败率里浮出水面

这不是某个团队的疏忽,这是协议工程整个生态的信息扩散结构的问题——同一个 bug 在三个独立实现里被独立踩中过。HN 上有人提议:是不是该用 TLA+ 之类的形式化验证给这些算法做证明?很多深思熟虑的回复指向了一个更现实的答案:拥塞控制算法不仅要"正确",更要在真实互联网那种复杂噪声里"经久"——形式化验证给出的是"前置假设满足时的正确性",但拥塞控制 bug 的本质往往是前置假设悄悄被环境违反(比如这次的"idle 等价于 bytes_in_flight == 0")。

这也是我之前写过的《Copy Fail 与后量子 IPsec:内核态信任根的双向时间旅行》里反复强调的——内核里的代码在被复制到其它地方之前,承载着大量"上下文化"的隐含假设,一旦离开它原来的上下文,那些假设就会变成隐患。

7. 为什么必须写"残忍"的测试

整个故事里最容易被读者跳过、但其实最重要的,是那个故意制造 30% 丢包前两秒、再让网络变干净的测试场景

普通的"下载 10MB 文件,看吞吐"测试根本测不出这个 bug——因为它根本进不去最小 cwnd 状态。要触发死亡螺旋,必须先把连接打下去,再让网络恢复,看它能不能爬起来。

这种"故意把系统逼到角落里"的测试设计有一个简洁的名字:adversarial testing / corner-state coverage。它在拥塞控制、共识协议、分布式锁、加密握手这些"控制平面"的子系统里是必修课。HN 上有评论一针见血:

原文:The test is the hardcore engineering tell here. The test is dialed in on the key area, and when the graph wasn’t coming out the right shape, they kept at it.(来源:news.ycombinator.com 评论区)

可观察性工程师 Charity Majors 几年前讲过一句话,可以放在这里:“系统的可靠性,是你测试过的最差情况”。这个 14ms 的死亡螺旋之所以最终被修掉,不是因为它在生产环境造成了什么巨大事故,而是因为有人选择了去模拟"出事之后能不能恢复"这个 regime

8. AI 写作风格争议——一个不能回避的元话题

要诚实地说:HN 评论区里有相当一部分讨论不是关于 CUBIC 的,而是关于这篇博客的"AI 写作味"。读者注意到:标题党式的小节副标题、对每个段落都"总结一下要点"的冗余、词汇之间过度变体(同一个意思用 “showing”、“revealing”、“demonstrating” 轮替)、以及代码里出现的 em-dash。有人吐槽:

原文:The more precise title should be: How we copied code from Linux kernel without fully understand it and missed its follow-up fixes, now it bites us.(来源:news.ycombinator.com 评论区)

这条吐槽有它的合理之处。但我读完原文的整体感受是:核心的技术内容、关键的 commit 引用、qlog 可视化的诊断逻辑都是真实工程师才能写出来的——AI 顶多被用来润色和扩写"过渡段"。这反过来也是 2026 年技术写作的一个新现实:当 AI 协助写作变成默认配置,读者会本能地把每一篇博客都当成"人机协作产物"来读。这不是 Cloudflare 一家的问题,而是整个技术写作生态正在适应的一种新读法。

这点和我之前写过的《Emacs 化的软件世界》的论点其实是一脉相承的:当 AI 让生产变得边际为零,风格、品味、克制本身就成为了稀缺资源

延伸阅读图谱

作者 Esteban Carisimo / Antonio Vicente / quiche 团队的相关作品

  1. Making Rust Workers reliable: panic and abort recovery in wasm-bindgen(2026-04-22):同一个团队最近的另一篇深度调试故事,讲 Rust panic 在 WebAssembly Workers 里的恢复语义。
  2. Agents Week: network performance update(2026-04-17):Cloudflare 把请求处理层迁移到 Rust-based FL2 架构的总体回顾。
  3. Launching Cloudflare’s Gen 13 servers: trading cache for cores(2026-03-23):硬件层的"用 cache 换 cores"决策,背后同样是 quiche/FL2 Rust 栈带来的效率红利。
  4. quiche GitHub repo:Cloudflare 开源的 QUIC + HTTP/3 实现,本文的修复已经合入。
  5. Even faster connection establishment with QUIC 0-RTT resumption:早期介绍 quiche 设计的奠基性博客。

相关论文与上游材料

  1. RFC 9438: CUBIC for Fast and Long-Distance Networks:CUBIC 的正式 IETF 标准化文档。
  2. BBR: Congestion-Based Congestion Control(Google, 2016):BBR 作为 model-based 替代品,正面回避了"loss == congestion"的假设。
  3. CUBIC: a new TCP-friendly high-speed TCP variant(Ha, Rhee, Xu, 2008):CUBIC 原始论文。
  4. Linux kernel commit history:可以在 git log 里搜 bictcp_cwnd_eventtcp_cubic 看到 2017 年那两个相关 commit 的完整讨论。
  5. Jepsen analysis of Redis-Raft:分布式协议在"故意 adversarial"场景下出问题的最经典案例之一,和本文测试设计哲学一脉相承。

反方观点 / 不同视角

  1. HN 评论 blahgeek 的尖锐版本:标题应该是 “How we copied code from Linux kernel without fully understand it and missed its follow-up fixes”——这是一个值得认真接受的批评。
  2. The Limits of Rust(Sylvain Kerkour, 2026-05):与本文形成有趣对照——Cloudflare 用 Rust 重写 user-space QUIC 取得了显著性能收益,但 Kerkour 的反方论点是中小团队不应该模仿 Amazon/Cloudflare 这种规模选择。
  3. AI 写作风格批评:HN 评论区对"AI 痕迹明显"的讨论本身就是这篇博客的一种二阶解读,提醒读者技术博客的"工程师味"正在变得稀缺。

编辑延伸思考:从一个 CUBIC bug 看 2026 年的"协议工程文化"

读完这篇文章我合上电脑想了很久。这个 bug 表面上是 quiche 的,本质上是整个网络协议生态在 user-space 化、Rust 化、QUIC 化的过程中正在经历的一种范式震荡的一个缩影。

过去,拥塞控制是 TCP 的,是内核态的,是一家一家供应商在 Linux 内核里慢慢演化、互相 review、互相 backport 的。这套生态信任的不是"算法证明",而是"几十亿条真实连接跑过它"。修一个 bug,是一个 commit,全世界几年内同步过去。

现在,QUIC 在 user-space。每个大厂的 QUIC 实现(quiche、msquic、quic-go、neqo、Google QUIC、Meta 的 mvfst)都有自己的拥塞控制器代码——它们大多数都"抄自"Linux 内核 CUBIC,但抄完之后就和内核失去了同步。Linux 内核里的 CUBIC 还在被 Google、Facebook、AWS 的工程师持续修补;而 user-space 那些拷贝品,每一份都在按自己的节奏,独立踩同一个坑。

这种"协议碎片化"对性能优化是好事——quiche 可以为 QUIC 量身定制状态机,可以 BBRv3 实验性上线,可以做 0-RTT 优化,不必背 TCP 那套兼容性。但代价是:bug fix 的扩散速度变慢了。2017 年内核里那个 follow-up commit,在 quiche 这边过了 9 年才被发现是缺的。生态里有多少其它 user-space 实现也漏抄了?没人知道。

这就引出一个更深的问题:协议工程的"知识载体"应该是什么?

  • 是 RFC 文档?RFC 9438 写了 CUBIC 算法的所有公式,但没有写那个"epoch 不能设到未来"的实现细节,因为那是 implementation note,不是算法本质。
  • 是 reference implementation?Linux 内核 CUBIC 现在事实上就是 reference,但它用 C 写,且和内核态语义(CA_EVENT_TX_START)深度耦合,user-space 移植必然失真。
  • 是测试套件?目前 IETF QUIC Interop Runner 测的主要是 wire-level 互操作性,不测稳态外的算法行为
  • 还是社区记忆?HN 上 vasilvv 那句"我 2015 年就发现过这个 bug" 之所以能存在,是因为他正好在评论区刷到了这篇文章。换个时间、换个频道,这种知识就丢了。

我个人的观察是:user-space 协议生态需要一个"实现行为级"的 conformance test suite,不止测 wire format,也要测"在最小 cwnd 下能否恢复"、“在大量丢包后 epoch 是否正确推进"这种算法行为。Cloudflare 这次写的 30% loss 测试就是一个原型——它应该被推到 IETF 层级,成为所有 QUIC 实现的过关测试。这件事很像我之前写过的《AI Evals as the new compute bottleneck》——evals 才是知识真正能扩散开的载体,不论是 LLM 还是协议。

另一个延伸思考是:Rust 重写不是"安全的银弹”,是"语义的新平台"。quiche 用 Rust 写不会自动避免 CUBIC 的逻辑 bug,因为 borrow checker 不知道"bytes_in_flight == 0 不等于 idle"。Rust 给的是内存安全和类型安全——它阻止你"读到没初始化的指针",但不阻止你"在错误的语义层做了一个看似无害的假设"。这次的死亡螺旋是后者,不是前者。这点和我之前写过的《Rust 异步生态的分裂与重聚》讨论的是一类问题:语言安全只是基础设施,真正难的是领域语义

最后想说的是关于"诚实写技术博客"这件事。HN 上对本文 AI 写作味的批评是公允的——某些过渡段落确实显得"被填充过"。但同时我也想替 Cloudflare 工程团队说一句:他们至少诚实地把 commit message 引出来、把代码贴出来、把 RTT-周期的观察讲清楚了。在 2026 年这个"博客 SEO + AI 摘要"双重压力下,能写一篇要求读者自己思考"为什么 14ms 等于 RTT 这个事实重要"的文章,已经是少数。

把这种写作和vibe content marketing 区分开很重要。Cloudflare 之前那篇被广泛传阅的 Code Orange 文章(我专门写过一篇导读)就是这种"真有内容的工程博客"的范本——它不解释"我们多么努力",而是把流程、数字、决策都摆出来让你自己评判。这篇 QUIC 文章在叙事节奏上稍弱,但技术诚实度仍然在线

配套资料导览

我为这篇文章准备了四份配套资料,放在同一目录下:

  • mindmap.svg:一张思维导图,把 CUBIC 算法、idle 检测、quiche 移植、min-cwnd 死亡螺旋、修复方案五条主线串成一张图,深色背景方便夜读。
  • concept-cards.md:12 张关键概念卡片,覆盖 cwnd、epoch、RTT、ACK clock、congestion avoidance、recovery、BBR、quiche、qlog 等核心术语,每张卡片 3-5 句话讲清是什么、为什么重要、典型陷阱。
  • glossary.md:英中对照术语表,约 35 条,方便中文读者快速查 CUBIC / Reno / BBR / RTO / RACK 这些缩写词。
  • cover.svg:封面图,深色底配 cwnd 死亡螺旋示意线条与"好文共赏 · QUIC 死亡螺旋"主标题。

谁应该读这篇文章

  • 网络协议方向的工程师:必读。这是少数从 commit history 一路讲到 user-space 移植坑的实操文章。
  • SRE / 平台工程师:强烈推荐。“测系统从崩溃中爬出来"这条经验值千金。
  • Rust 系统工程师:推荐。看 quiche 怎么用 Rust 实现拥塞控制状态机、为什么 Rust 不能自动避免这类逻辑 bug。
  • AI / 数据平台工程师:选读。RTT-时钟、ACK clock 这些概念在分布式 training 和 RPC 调度里有强对偶。
  • 管理者 / 技术 lead:选读"为什么必须写残忍的测试"那一节——它可以直接当作给团队讲"为什么投资可观察性"的素材。
  • 想看 AI 协助写作生态变化的人:可以同时读 HN 评论区,作为 2026 年技术写作风格演化的一个数据点。

愿每一个看似简单的"if bytes_in_flight == 0”,背后都有人愿意花几周时间,看 999 条状态翻转线,最后写下那三行代码。

而你下次读到一篇技术博客时,也愿意多看一眼那个 qlog 图表上的横轴。