Swap 导致 Go GC 停顿 40ms 的真相

A 40ms Go garbage collector pause caused by swap

Swap 导致 Go GC 停顿 40ms 的真相

我差点在 production 环境中因为开启 Swap 而酿成大祸。本以为 cgroup 级别的 Swap 只会轻微影响性能,结果发现 Go 的垃圾回收器在读取元数据时,如果这些元数据被换出到 Swap,会导致长达 40ms 的 stop-the-world 停顿。这 40ms 里,228 次缺页中断几乎耗尽了全部时间,导致所有 goroutine 停滞,甚至可能错过 I/O 返回。更糟糕的是,构建消息的时间也从几毫秒飙升到近一秒。虽然 Swap 本身并非邪恶,但在高负载的 Go 应用中,它与 GC 的配合确实存在致命隐患。

那些 40 毫秒看似微不足道,但这是 stop-the-world 停顿,意味着一切都已停止。
  1. nasretdinov

    不幸的是,这在某种程度上是可以预见的——无论具体实现如何,GC 必须遍历整个树来追踪仍被引用的部分,这一事实本身就使得 swap 变得极不实用——即使没有 40ms 的 stop-the-world 停顿,swap 使用的 LRU 缓存也会在每次 GC 时被清空。

    我认为这实际上也是 Apple 放弃在框架中使用 GC 转而采用自动引用计数(ARC)的原因。

  2. jacobgold

    "这样做会让我很痛"

    "那就别做了"

    如果你关心延迟,就关闭 swap。系统范围关闭,或者针对特定的 cgroup 关闭。

  3. pizlonator

    Go 为什么不使用无 STW(Stop-The-World)的即时 GC(on the fly GC)呢?有什么原因吗?

  4. xavdid

    Discord 早在 2020 年就学到了这一课,并发布了一篇很棒的博客文章:https://discord.com/blog/why-discord-is-switching-from-go-to...

  5. soltanov

    GC 延迟的 SLO 应该包含操作系统内存压力。否则,缺页中断问题会被误认为是收集器问题,从而导致错误的修复方案。

  6. Gabrys1

    我开始琢磨是否能有感知 swap 的 GC,比如先让所需的页面 swap 进来(虽然目前似乎没有明显的 API 能做到这一点...),然后再暂停世界?

  7. imclaren

    没错。在 Go 中修复内存膨胀 bug 相对容易,但有时移除 swap 带来的膨胀却是一场冒险。而且我认为完全移除 swap 带来的膨胀是值得的!

  8. faangguyindia

    我用 Go 的地方,原本是用 Node、Ruby 或 Python 的。

    我需要用低延迟的地方,就用 Rust。

同日更多故事

2026-10-05