前言
在将Pinterest的搜索基础设施(该系统每月为数百万用户提供核心体验)迁移至Kubernetes的过程中,我们在这个新环境中遇到了一个挑战:每百万次搜索请求中就有一次的处理时间比正常情况长100倍。
本文记录了我们的调查过程,揭示了一个内存密集型搜索系统与一个看似无辜的监控进程之间难以察觉的交互问题。整个排查过程涉及对搜索系统的性能剖析、性能问题调试、Linux内核特性以及内存管理机制等方面。
一、将 Manas 迁移到 Kubernetes
在Pinterest,搜索功能是推荐系统的重要组成部分。当用户浏览主页、输入搜索词或查看相关内容时,所呈现的结果通常来自搜索功能。
为了满足Pinterest如此大规模的搜索需求,我们自主研发了一套名为Manas的搜索系统。如今,Manas为数十个搜索索引提供支持,助力Pinterest内部众多团队构建高性能的推荐功能,是Pinterest最重要的服务之一。Manas的核心在于其定制的集群管理解决方案,该方案管理着分布在数千台主机上的100多个搜索集群。
自2017年构建以来,这套定制的集群管理系统变得越来越复杂、不透明并且容易出错。为解决这一问题,Manas被迁移至Pinterest自研的Kubernetes平台PinCompute。2024年,我们基于Envoy、Spinnaker等开源技术构建新系统,并集成了新的定制组件,例如GitOps配置管理系统和专为Manas设计的Kubernetes Operator。
2025年伊始,我们对前期工作成果进行验证并开展性能测试。然而,在向测试集群发送流量时,我们发现了一个问题:一小部分但持续存在的请求发生超时。
以当前规模而言,每分钟出现几次超时,就会导致每分钟都有部分用户接收到质量略有下降的推荐内容。虽然在短期内可能不会立即影响各项指标,但其潜在影响不容忽视——轻则一直影响Pinterest所有推荐服务;重则问题可能逐步恶化,最终演变为全公司范围内的持续性故障。鉴于风险过高,我们不得不暂停迁移进程来找出根本原因。
二、开始调查——解析请求延迟
为了清楚请求超时的原因,我们需要分析Manas是如何处理搜索请求的。
首先,Manas采用双层架构:根节点处理服务请求(通过叶节点扇出),而叶节点则负责处理特定搜索索引的一个分片。
Manas 采用双层扇出架构——根节点将请求扇出到每个分片的叶节点,然后聚合并返回响应。
叶节点处理请求分为四个阶段:处理查询→从内存映射查找中检索候选对象→通过额外的内存映射查找对候选对象进行排名和水合→最后将结果返回给根节点
叶节点查询处理——查询处理分为四个阶段,其中索引检索、排名和水合过程都会执行索引查找。
现在分析超时情况:汇总所有叶节点的数据后发现,最大服务延迟值异常偏高,而 p9999 及以下的延迟值则处于正常范围内。具体而言,我们预期最大延迟应低于60毫秒,但实际却高达5秒,几乎高出100倍,这表明叶节点层面存在异常。
汇总的叶节点服务延迟(顶部红色部分:P100;下方几乎看不见:P99 及其他节点)
随后我们放大观察某一特定叶节点,记录其在短时间间隔内的检索与排序阶段,发现原本平滑的P100曲线衰减为每隔几分钟出现的尖锐潜伏期峰值,并在峰值之间回落至接近正常水平。
单个叶节点的检索延迟(即搜索索引耗时)——每隔几分钟出现一个尖锐的 P100 峰值(顶部红色:P100;下方几乎看不见:P99 和其他)
单节点叶子排序延迟(即对候选节点进行排序所需的时间)——每隔几分钟就会出现明显的P100峰值(顶部红色部分:P100;下方几乎看不见:P99 和其他节点)
这完全出乎意料。检索与排序均在每个请求的单线程中完成的,因此延迟异常的原因可能有两种:一是CPU之外的操作受到外部因素干扰,二是某些因素导致操作运行缓慢。
我们向每个叶节点发送了数万个请求,每个节点每隔几分钟便出现一次最大延迟峰值,该异常由单次请求触发的概率约为百万分之一,这种情况比较罕见。
三、缩小问题范围
为了系统性地应对该问题,我们先简化测试环境以排除潜在的变量。
此前已为Manas Pod配置了节点独占访问权限与专属网络资源,接下来将测试搜索集群重新配置到内存更大的EC2实例类型上,确保将完整索引加载至内存中运行,从而最大限度减少页面错误。同时,Kubernetes节点采用与当前生产环境中EC2 Manas集群相同的AMI,并取消了CPU和内存的cgroups限制,甚至直接在Kubernetes节点上运行Manas,而不是在容器中。
但是,上述调整未产生任何效果,延迟问题依然存在。这表明某些因素正在干扰Manas,而这种干扰与基于AMI的Kubernetes配置有关。
随后我们采取双管齐下的排查策略:一方面通过操作系统级别的性能分析进行白盒测试;另一方面通过持续排查Kubernetes节点与当前生产环境节点之间的潜在变量进行黑盒测试。
1、白盒测试
采集CPU、内存和网络利用率数据,并将其与延迟峰值进行关联。通过使用性能分析工具(perf)观察CPU调度事件,对比了Kubernetes与当前生产环境的抢占率差异;同时,检测了两个系统之间的缓存利用率和内核锁竞争情况。
2、黑盒测试
通过taskset和CPU指令集对Manas pod进行CPU屏蔽,使其与其他进程隔离,最终使Manas主进程能够独占访问节点上几乎所有CPU。如前所述,我们还直接在主机上运行Manas二进制文件(完全脱离其容器),采用类似的屏蔽机制,从而避免了cgroups限流的可能性。
但这两种方法均未奏效。我们没有发现明显的CPU占用或抢占问题迹象,所采取的隔离与防护措施也未产生任何明显改善效果。
不过,我们所掌握的信息仍具有价值,并且问题排查已接近核心,可以确认的是:
1)不存在用户空间资源限制,网络、内存、磁盘 I/O 等方面均无瓶颈;
2)尽管不清楚具体是哪个组件或机制,但能确定问题出现在基于AMI部署的Kubernetes附加组件中;
3)该问题是暂时性的。
四、定位问题根源
至此,我们采取了强制性措施:通过依次执行sudo kill -STOP命令,暂停或终止所有非必要进程,包括日志服务、统计管道、安全代理、kubelet 以及 Pinterest专属守护进程。由于终止这些进程会导致统计信息缺失,我们采用根节点作为叶子节点健康状况的参考指标。
最终定位到问题根源:cAdvisor。停用该程序后,所有延迟峰值立即消失。
聚合叶节点服务延迟(减少叶节点数量,减少插值)——禁用cAdvisor后,P100收敛到P99水平(顶部红色:P100;下方几乎看不见:P99和其他节点)
排查过程终于取得进展。
五、根因深度剖析
但是,即便找到问题根源,我们也无法从PinCompute中永久移除cAdvisor。作为导出容器级监控指标的核心组件,cAdvisor的停用会导致监控能力及工作负载自动扩缩容等功能失效。
幸运的是,观察该问题的过程简单直接,并且为cAdvisor本身存在的潜在问题提供了很好的线索。
通过perf工具分析,我们发现根本问题在于cAdvisor调用了smaps Linux内核功能,在内存信息分析上耗费了过多时间。
非Manas进程占用CPU的Perf top分析显示,绝大部分CPU时间消耗在与内存相关的内核跨度上。
阅读cAdvisor文档时,有问题的相关指标已被明确标注为警告。
cAdvisor导出的指标文档里,将 container_referenced_bytes 该指标描述为一种侵入性较强的内存采集指标。
container_referenced_bytes 是cAdvisor的默认启用指标,用于跟踪进程在每个测量周期中引用的内存总字节数。该指标通过页表项(PTE)访问位机制,实现布伦丹·格雷格(Brendan Gregg)所提出的工作集大小(WSS)估算:该机制利用页表中访问位的条目来计算进程读取或写入的内存总量。
每次cAdvisor运行时,它都会扫描整个页表,统计所有页表项的访问位以计算此统计信息,随后清除这些访问位。PinCompute每30秒运行一次cAdvisor,这意味着这种侵入式的访问位检查和清除操作每分钟会发生两次。
Manas的搜索索引在每个叶节点上可达数百GB,二级排序索引甚至超过1TB。Manas叶节点启动时,会将整个索引映射到内存中。这意味着内存占用可能相当大(例如,对于一台已用内存量为100GB的主机,页表可能容纳2500万条记录)。如果每30秒遍历并清除2500万条页表记录,无疑会与内存密集型的叶节点处理产生冲突。事实上,布伦丹已经明确指出这一点,并警告说他的工具主要用于实验。
上图是布伦丹·格雷格的明确警告,描述了WSS收集机制的副作用和风险。
总之,每次调用 smaps 时都会在扫描整个内核页结构的过程中持有锁,而每次调用 clear_refs 时也会在扫描整个内核页结构时持有锁,并在此过程中逐页刷新工作集内的所有 TLB 缓存。
这很有可能是导致Manas叶节点二进制进程(映射内存超过100GB)在每百万次请求中约有1次出现延迟飙升至正常值100倍的原因,其根源正是cAdvisor触发的内存资源争用。
调查至此告一段落。尽管仍有很多方向值得深入探索,例如剖析内核源码、引入并测试更多的检验工具等。但既然问题已经解决,并且我们认为投资回报率并不高,便不再深入探索。其实并非每个问题都需要完美的解决方案,足够好的方案往往能事半功倍。虽略有遗憾,但及时解除阻碍、继续前行,有利于我们实现更长远的目标:升级Pinterest工程师所依赖的基础设施,从而更好地服务用户。
六、问题解决
确定根本原因后,我们禁用了所有PinCompute节点的cAdvisor WSS估算功能,仅需修改一行代码即可解决问题。同时,我们还在cAdvisor代码库中提交了GitHub Issue来分享相关经验。
虽然Manas是第一个在PinCompute上遇到该问题的系统,但它更像是一个预警信号,率先暴露了问题所在。若未及时解决,这种性能下降会阻碍其它所有内存密集型系统迁移到我们的平台。对Pinterest的Kubernetes平台而言,实现稳定的服务时延是一个重要的里程碑。Manas作为高负载分布式系统,如果能够稳定地运行,会为后续将更多在线服务迁移至该平台奠定基础。
七、经验反思和教训
1)资源隔离具有挑战性:例如,CPU屏蔽并不能完全隔离进程与相邻进程;
2)缩小问题范围:确认问题并非由PinCompute AMI引起后,有助于缩小问题范围,避免白费力气;
3)黑盒测试是有效的:不要忽视破坏性测试等基础策略,这种方式实则是上述二分法思路的具象化应用。
作者丨Samson Hu, Shashank Tavildar, Eric Kalkanger, Hunter Gatewood
来源丨https://medium.com/pinterest-engineering/debugging-the-one-in-a-million-failure-migrating-pinterests-search-infrastructure-to-kubernetes-bef9af9dabf4
dbaplus社群欢迎广大技术人员投稿,投稿邮箱:editor@dbaplus.cn
如果字段的最大可能长度超过255字节,那么长度值可能…
只能说作者太用心了,优秀
感谢详解
一般干个7-8年(即30岁左右),能做到年入40w-50w;有…
230721