Swap 导致 Go GC 停顿 40ms 的真相
A 40ms Go garbage collector pause caused by swap

我差点在 production 环境中因为开启 Swap 而酿成大祸。本以为 cgroup 级别的 Swap 只会轻微影响性能,结果发现 Go 的垃圾回收器在读取元数据时,如果这些元数据被换出到 Swap,会导致长达 40ms 的 stop-the-world 停顿。这 40ms 里,228 次缺页中断几乎耗尽了全部时间,导致所有 goroutine 停滞,甚至可能错过 I/O 返回。更糟糕的是,构建消息的时间也从几毫秒飙升到近一秒。虽然 Swap 本身并非邪恶,但在高负载的 Go 应用中,它与 GC 的配合确实存在致命隐患。
那些 40 毫秒看似微不足道,但这是 stop-the-world 停顿,意味着一切都已停止。
HN 评论区
108- nasretdinov
不幸的是,这在某种程度上是可以预见的——无论具体实现如何,GC 必须遍历整个树来追踪仍被引用的部分,这一事实本身就使得 swap 变得极不实用——即使没有 40ms 的 stop-the-world 停顿,swap 使用的 LRU 缓存也会在每次 GC 时被清空。
我认为这实际上也是 Apple 放弃在框架中使用 GC 转而采用自动引用计数(ARC)的原因。
- jacobgold
"这样做会让我很痛"
"那就别做了"
如果你关心延迟,就关闭 swap。系统范围关闭,或者针对特定的 cgroup 关闭。
- pizlonator
Go 为什么不使用无 STW(Stop-The-World)的即时 GC(on the fly GC)呢?有什么原因吗?
- xavdid
Discord 早在 2020 年就学到了这一课,并发布了一篇很棒的博客文章:https://discord.com/blog/why-discord-is-switching-from-go-to...
- soltanov
GC 延迟的 SLO 应该包含操作系统内存压力。否则,缺页中断问题会被误认为是收集器问题,从而导致错误的修复方案。
- Gabrys1
我开始琢磨是否能有感知 swap 的 GC,比如先让所需的页面 swap 进来(虽然目前似乎没有明显的 API 能做到这一点...),然后再暂停世界?
- imclaren
没错。在 Go 中修复内存膨胀 bug 相对容易,但有时移除 swap 带来的膨胀却是一场冒险。而且我认为完全移除 swap 带来的膨胀是值得的!
- faangguyindia
我用 Go 的地方,原本是用 Node、Ruby 或 Python 的。
我需要用低延迟的地方,就用 Rust。