【Node Weekly】 How we 30x'd our Node parallelism by Evan Limanto

【Node Weekly】 How we 30x'd our Node parallelism by Evan Limanto

How We 30x-ed Our Node Parallelismnode

 

安全地提升生产节点服务的并行性的最佳方法是什么?这是几个月前个人团队须要回答的问题。web

 

咱们为咱们的银行集成服务运行4000个节点容器(或“工人”)。该服务最初的设计使每一个工做人员一次只能处理一个请求。这种设计减小了意外阻塞事件循环的集成的影响,并容许咱们忽略不一样集成中资源使用的可变性。可是,因为咱们的总容量被限制在4000个并发请求,因此系统没有适当地扩展。大多数请求都是网络绑定的,因此若是咱们能找出如何安全地提升并行性,就能够提升咱们的容量和成本。正则表达式

 

在咱们的研究中,咱们找不到一个好的剧原本描述节点服务从“无并行”到“大量并行”。所以,咱们制定了本身的计划,它依赖于仔细的计划、良好的工具和可观察性,以及健康的调试。最终,咱们可以将并行度提升30倍,这至关于每一年节省约30万美圆的成本。这篇文章将概述咱们如何提升节点工做人员的性能和效率,并描述咱们在这个过程当中吸收的教训。promise

 

为何咱们投资并行

若是咱们不使用并行性就走到了这一步,这可能会让人惊讶——咱们是如何作到的?只有10%的Plaid的数据拉涉及到一个用户谁在场,并将他们的账户连接到一个应用程序。其他的是在用户不在场的状况下进行的按期事务更新。经过在负载平衡层中添加逻辑,将用户当前请求优先于事务更新,咱们能够以牺牲事务新鲜度为代价处理1000%或更多的API峰值。安全

 

虽然这种权宜之计已经奏效了很长时间,但咱们知道,有几个痛点最终会影响咱们的服务可靠性:服务器

 

1. 来自咱们客户的API请求峰值愈来愈大,咱们担忧一个协调的流量峰值会耗尽咱们的工做人员的能力。websocket

2. 银行请求的延迟峰值一样致使咱们的工做人员容量降低。因为银行基础设施的可变性,咱们对出站请求设置了保守的超时,完整的数据拉取可能须要几分钟。若是一家大型银行的延迟时间猛增,就会有愈来愈多的员工被困在等待回应。网络

3. ECS的部署时间变得很是缓慢,即便咱们提升了部署速度,咱们也不想进一步增长集群的大小。并发

咱们认为提升并行性是消除应用程序瓶颈和提升服务可靠性的最佳方法。做为反作用,咱们相信咱们能够下降基础设施成本,实施更好的可观测性和监测,这两项措施都将在将来带来回报。socket

 

咱们如何可靠地推出更改

工具和可观测性

咱们有一个自定义的负载平衡器,它将请求路由到咱们的节点工做器。每一个节点工做机运行一个gRPC服务器来处理请求,并使用Redis将其可用性通告回负载平衡器。这意味着添加并行性就像更改几行逻辑同样简单:工人应该继续公布可用性,直处处理N个正在运行的请求(每一个请求都有本身的承诺),而不是在接到任务后当即报告不可用。

 

不过,事情没那么简单。咱们的首要目标是在任何一次发布中保持可靠性,咱们不能仅仅增长并行性。咱们预计此次发布将特别危险:它将以难以预测的方式影响咱们的CPU使用率、内存使用率和任务延迟。因为Node的V8运行时在事件循环上处理任务,咱们主要关心的是,咱们可能在事件循环上作了太多的工做,从而下降了吞吐量。

 

为了下降这些风险,咱们确保在第一个并行工做人员部署到生产环境以前,已经准备好了如下工具和可观察性:

 

1. 咱们现有的麋鹿堆栈有足够的现有日志进行特别的调查。

2. 咱们添加了一些普罗米修斯指标,包括:

  • 使用process.memoryUsage()的V8堆大小
  • 使用gc stats包的垃圾收集统计信息
  • Task latency statistics任务延迟统计信息,按银行集成类型和并行级别分组,以可靠地测量并行性如何影响咱们的吞吐量。

3. 咱们建立了Grafana仪表板来测量并行性的效果。

4. 更改应用程序行为而没必要从新部署咱们的服务是很是关键的,所以咱们建立了一组启动功能标志来控制各类参数。以这种方式调整每一个工做人员的最大并行度,最终使咱们可以快速迭代并找到最佳并行度值——在一分钟内。

5. 为了了解应用程序各个部分的CPU时间分布,咱们在生产服务中构建了flamegraphs。

  • 咱们使用了0x包,由于节点检测很容易集成到咱们的服务中,生成的HTML可视化是可搜索的,而且提供了很好的详细程度。
  • 咱们添加了一个分析模式,其中一个工做人员将开始启用0x,并将产生的痕迹写入S3退出。而后,咱们能够从S3下载这些日志,并在本地使用x查看它们—仅可视化。/flamegraph
  • 咱们一次只运行一个工人的配置文件。CPU分析提升了资源利用率并下降了性能,咱们但愿将此影响隔离到单个工做进程。

 

启动卷展栏

在这项初步工做完成以后,咱们为咱们的“并行工做者”建立了一个新的ECS集群。这些是使用SunCurkLy特征标志来动态设置它们的最大并行性的工做者。

 

咱们的推出计划包括逐渐将愈来愈多的流量从旧集群路由到新集群,同时密切监视新集群的性能。在每一个通讯量级别上,咱们将调整每一个工做机上的并行性,直到它尽量高,而不会致使任务延迟或其余指标的任何降级。若是咱们发现问题,咱们能够在几秒钟内动态地将流量路由回旧集群。

 

正如所料,在这一过程当中出现了一些挑战。咱们须要调查和解决这些棘手的问题,以便有意义地提升咱们的并行性。这才是真正有趣的开始!

 

部署,调查,重复

增长节点的最大堆大小

当咱们开始推出过程时,咱们开始获得警告,咱们的任务已经退出了一个非零代码-一个吉祥的开始。咱们深刻基巴纳发现了相关的log (associated log):

 

 

这让咱们想起了过去咱们经历过的内存泄漏,其中V8在吐出相似的错误信息后会意外地退出。它有不少意义:更大的任务并行性致使更高的内存使用率。

 

咱们假设从默认1.7GB增长节点最大堆大小可能会有所帮助。为了解决这个问题,咱们开始运行最大堆大小设置为6GB(--max old space size=6144命令行标志)的节点,这是一个任意较高的值,仍然适合咱们的EC2实例。令咱们高兴的是,这修复了生产中的“分配失败”消息。

 

识别内存瓶颈

随着内存分配问题的解决,咱们开始看到并行工做机上的任务吞吐量很低。咱们仪表盘上的一张图表马上就很显眼。这是并行工做进程的每一个进程堆使用状况:

 

 

 

其中一些线不断增加直到它们达到最大堆尺寸-坏消息!

 

咱们在Prometheus中使用了系统度量来排除文件描述符或网络套接字泄漏是根本缘由。咱们最好的猜想是,GC在旧对象上发生的频率不够,致使工做进程在处理更多任务时积累更多分配的对象。咱们假设这会下降咱们的吞吐量,以下所示:

 

  • 工人接收新任务并执行某些工做
  • 在执行任务时,一些对象被分配到堆上
  • 因为(还没有肯定)激发和忘记操做未完成,在任务完成后将维护对象引用
  • 垃圾收集变得更慢,由于V8须要扫描堆上的更多对象
  • 因为V8实现了一个stop-the-world GC,新任务不可避免地将得到更少的CPU时间,从而下降工做线程的吞吐量

咱们搜索了咱们的代码 for fire-and-forget operations,也被称为“浮动承诺”。这很简单:咱们只是在代码行中寻找禁用了无浮动承诺linter规则的代码行。有一种方法特别吸引了咱们的注意。它生成了一个叫compressandploaddingpayload的调用,不用等待结果。这个调用彷佛很容易在任务完成后很长时间内继续运行。

 

 

 

咱们想验证咱们的假设,即这些浮动承诺(floating promises)是瓶颈的主要来源。假设咱们跳过了这些对系统正确性并不重要的调用,那么任务延迟会获得改善吗?如下是临时删除对postTaskDebugging的调用后堆的使用状况

 

 

 

答对 了!咱们的并行工做线程上的堆使用如今在很长一段时间内保持稳定。

 

彷佛有一个compressandploaddingpayload调用的“backlog”,随着相关任务的完成,这个调用会慢慢创建起来。若是一个worker接收任务的速度超过了它修剪这个backlog的速度,那么在内存中分配的任何对象都将永远不会被垃圾收集,从而致使咱们在前面的图中观察到的堆饱和。

 

咱们开始怀疑是什么让这些浮动的承诺如此缓慢。咱们不肯意从代码中永久删除compressandploaddingpayload,由于这对于帮助工程师调试本地机器上的生产任务相当重要。从技术上讲,咱们能够在完成任务以前等待此调用,从而消除浮动承诺,从而解决问题。然而,这会给咱们正在处理的每一个任务增长一个很是重要的延迟量。

 

保留这个潜在的解决方案做为备份计划,咱们决定研究代码优化。咱们怎么能加快行动?

 

修复S3瓶颈

compressandploaddingpayload中的逻辑很容易理解。咱们压缩调试数据,由于它包含网络流量,因此可能会很是大。而后咱们将压缩数据上传到S3。

 

 

 

不认为套接字是瓶颈,由于Node的默认HTTPS代理将maxSockets设置为无穷大。然而,咱们最终在AWS节点文档中发现了一些使人惊讶的东西:S3客户端将maxSockets从无穷大减小到了50。不用说,这不是最直观的行为。

 

因为咱们已经让咱们的工人完成了50个并发任务,上传步骤在等待S3的套接字时遇到了瓶颈。经过对S3客户端初始化的如下更改,咱们改进了上载延迟:

 

 

加快JSON序列化

咱们的S3改进减缓了堆大小的增长,但并无彻底解决问题。另外一个明显的罪魁祸首是:根据咱们的计时指标,前面代码中的压缩步骤有时会长达4分钟。这比每一个任务平均4秒的延迟要长得多。不相信这一步会花这么长时间,咱们决定运行本地基准测试并优化这段代码。

 

压缩包括三个步骤(使用节点流限制内存使用):JSON字符串化、zlib压缩和base64编码。咱们怀疑咱们使用的第三方字符串库——bfj——多是问题所在。咱们编写了一个脚本,在各类基于流的字符串库上执行一些基准测试(请参见此处的代码)。结果发现,这个包的名字是大友好JSON,它毕竟不是很友好。看看咱们从实验中获得的两个结果:

 

 

结果使人吃惊。即便是最小的测试,bfj也比另外一个包JSONStream慢5倍左右。咱们很快用JSONStream替换了bfj,并当即观察到性能的显著提升。

 

减小垃圾收集时间

随着咱们的记忆问题的解决,咱们开始关注在相同类型的银行集成中,并行工做人员和单个工做人员之间的任务延迟比率。这是一个苹果对苹果的比较,咱们的平行工人是如何表现的,因此一个接近1的比率给了咱们信心,进一步推出交通的平行工人。在这一点上,咱们的Grafana仪表盘是这样的:

 

 

 

注意,有些比率高达8:1,即便是在至关低的平均并行度下(此时大约为30)。咱们知道,咱们的银行集成没有执行CPU密集型工做,咱们的容器也没有受到任何其余瓶颈的限制,咱们能够想到。因为没有更多的线索,咱们寻找在线资源来优化节点的性能。尽管这类文章不多,但咱们偶然发现这篇博客文章详细介绍了做者如何在一个节点进程上达到60万个并发websocket链接。

 

尤为是--nouse idle通知的特殊用法引发了咱们的注意。咱们的节点进程是否花费了太多时间来执行GC?方便的是,gc stats包让咱们能够看到垃圾收集的平均时间:

 

 

 

讨论Node中不一样类型GC的技术细节,但这是一个很好的参考资料。实质上,清理常常运行,以清理堆中节点“新空间”区域中的小对象。

 

所以,垃圾收集在咱们的节点进程上运行得太频繁了。咱们能够禁用V8垃圾收集并本身运行它吗?有没有办法减小垃圾收集的频率?原来前者是不可能的,但后者是!咱们能够经过在节点中突破“半空间”的限制来增长新空间的大小(--max semi space size=1024命令行标志)。这容许在V8运行清理以前分配更多的短时间对象,从而减小GC的频率:

 

 

 

又一次胜利!增长新的空间大小致使垃圾回收时间急剧降低,从30%降低到2%。

 

优化CPU使用

通过这些工做,咱们对结果感到满意。在并行工做机上运行的任务的延迟几乎与在大约20个并行工做机上运行的任务的延迟相等。在咱们看来,咱们已经解决了全部的瓶颈问题,但咱们仍然不彻底知道生产中的哪些操做实际上在减缓咱们的速度。因为咱们没有悬而未决的成果,咱们决定调查分析咱们工人的CPU使用状况。

 

咱们在一个并行工做器上生成了一个flamegraph,瞧,咱们获得了一个整洁的交互式viz,咱们能够在本地使用它。花边新闻:它占用磁盘60MB!下面是咱们在x火焰图中搜索“logger”时看到的:

 

 

 

teal突出显示的条表示至少15%的CPU时间用于生成工做日志。咱们最终将这一时间减小了75%——尽管咱们是如何作到的,这是一个值得再发表一篇博文的话题。(提示:它涉及正则表达式和大量属性枚举。)

 

在这最后一点优化以后,咱们可以支持每一个工人最多30个并行任务,而不会影响任务延迟!

 

成果和教训

迁移到并行工做器使咱们每一年的EC2实例开销减小了大约30万美圆,并大大简化了咱们的体系结构。咱们如今在生产中运行的容器减小了大约30倍,并且咱们的系统对于来自客户的外部请求延迟或API流量峰值的增长更加健壮。

 

咱们在并行化银行集成服务时学到了不少:

 

1. 永远不要低估系统低层次度量的重要性。在发布过程当中可以监控GC和内存统计是很是重要的。

2. Flamegraphs火焰图太棒了!如今咱们已经检测了它们,咱们能够轻松诊断系统中的其余性能瓶颈。

3. 理解节点运行时使咱们可以编写更好的应用程序代码。例如,了解V8对象分配和垃圾收集模型是尽量重用对象的一个动机。有时须要直接与V8交互或使用节点命令行标志来开发一个强大的心智模型。

4. 确保阅读系统每一层的文档!咱们信任maxSockets上的节点文档,但在咱们发现AWS包重写了默认节点行为以前,咱们花了不少时间研究。每个基础设施项目彷佛都有这样一个“抓住”的时刻。

咱们但愿这篇博客文章为优化节点并行性和安全地推出基础设施迁移提供了一些有用的策略。若是你对这类具备挑战性的工程项目感兴趣,咱们将招聘!

相关文章
相关标签/搜索