Pinterest 工程师消除 CPU 僵尸进程,解决生产环境瓶颈
点击查看原文>
Pinterest 发布了一份详细的技术报告,阐述他们的工程师如何追踪并解决了导致机器学习训练任务崩溃的间歇性 CPU 资源饥饿问题。通过识别团队所说的“僵尸”(即由崩溃循环的默认代理所留下的内存泄漏 cgroups),工程师们成功地恢复了分布式计算平台的稳定性。
该问题表现为间歇性网络故障以及在 PinCompute 上的任务崩溃。PinCompute 是一个基于 Kubernetes 的平台,Pinterest 超过一半的离线机器学习工作负载都在该平台上运行。它每月会为这些任务预配数万个 Ray 集群。在某些用例中,由于弹性网络适配器(ENA)设备重置和数据包丢失,训练任务的成功率下降了超过 25%。初步调查工作遇到了麻烦,因为汇总的 CPU 利用率看起来很正常,掩盖了底层的故障。
由于无法通过高级仪表盘进行监控,基础设施团队转而使用 mpstat 进行逐核分析。调查发现,个别内核的系统 CPU 使用率会连续数秒达到 100%。这种行为非常棘手,因为如果处理 ENA 网络中断的某个内核达到饱和状态,驱动程序的 NAPI 轮询线程就可能会因为缺乏处理周期而受阻,从而触发 ENA 设备重置(这是一种事务完成操作停滞超过五秒时启动的自愈机制),进而导致连接中断,最终使 Ray 任务崩溃。
为了准确定位导致内核饱和的根源,团队利用了在 12 小时重现窗口内每两分钟运行一次的性能捕获。通过在 Netflix Flamescope 中可视化这些捕获的数据,工程师们得以深入查看网络重置触发的确切时刻。他们发现,通常情况下 CPU 占用率不足 1% 的 kubelet 进程,此时却飙升至约 6.5%。其中大部分时间都耗费在内核函数 mem_cgroup_nr_lru_pages 中。
调查最终将问题追溯到了其节点所使用的 AWS 深度学习 AMI。该基础镜像中包含一个默认启用的 Amazon ECS 代理,但 Pinterest 并未使用该代理。在每次重启时,该代理都会陷入崩溃循环并泄漏内存控制组(memcgs)。活跃使用的 memcgs 仅有 240 个,而“僵尸”memcgs 却累积了近 70000 个,这导致 kubelet 在每次 cgroup 状态同步时都必须遍历这份膨胀的列表,从而独占一个内核多达数秒之久。
该问题的解决方法相对简单,但需要对系统堆栈有一个深入的理解。为了解决了这一瓶颈,Pinterest 在基础镜像中禁用了 ECS 代理的 systemd 单元,并重启受影响的机器来清除累积的 cgroups 。自此之后,内存 cgroup 的数量稳定了下来,网络重启现象也没有再出现。这一经验表明,应用程序、编排器与内核之间的抽象层往往会掩盖真正的原因:在这个案例中,正是冗余的用户空间守护进程导致了内核状态泄漏。
虽然 Pinterest 这次是通过手动分析来解决问题的,但团队也意识到,就生产环境的可观测性而言,持续的、按时间索引的分析具有重要的价值。诸如 gProfiler(Pinterest 目前正与英特尔合作开发)以及基于 eBPF 的平台(如 Parca 和 Grafana Pyroscope)这样的工具,能够提供集群范围的全局可见性,从而缩短从症状到根本原因的排查路径,使工程师能够实时识别问题模式,而不必在故障发生后手动进行排查。
通过分享研究成果,Pinterest 工程团队强调,通常来说,在规模比较大的环境中,性能表现不仅取决于应用程序代码,也很大程度上取决于基础镜像的默认配置。他们的实践经验为软件工程师们敲响了重要的警钟:要对系统默认设置持质疑态度,并熟练掌握底层诊断工具。
原文链接:https://www.infoq.com/news/2026/05/pinterest-cpu-zombies-bottleneck/
本文来源:InfoQ