以往的性能问题大多都出现在数据库查询中,着实没想到这次会是因为 Puma 的配置不当。原文链接:https://step-by-step.tech/posts/performance-issue-for-puma-configuration
最近这一两周其实打击挺大的,因为这段日子笔者每天都花大量时间在优化回流的服务端系统。精力都集中在减少 N+1 查询上,几乎把常用接口的 N+1 问题都解决掉了,慢查询也优化到所剩无几。然而,系统的性能还是不稳定,每到峰值时期,响应时间犹如脱缰的野马径直往上飙。
每次优化掉一些耗时较长的请求,都会信誓旦旦地跟同事说“今天应该不卡了”,然而每次到了高峰期,总会啪啪打脸,有大量响应时间 400ms 以上的请求不断冒出来,按都按不住,整个系统几乎处于不可用的状态。老实说以前总觉得响应时间 500-600ms 没什么的,但是最近看到大量请求的响应时间超过 300ms 的时候,血压也会跟着升高。
优化进入到了死胡同,笔者也很纳闷,数据库慢查询几乎没多少了,CPU 的使用率也不是很高,内存也一直在 5 ~ 8G 之间徘徊,按理说我这 8 核 16G 的机器资源还有很大的盈余,升级机器也解决不了什么问题,实在是不知道性能瓶颈在哪。
一筹莫展之际,笔者突发奇想,不知道是不是 Puma 的 Worker 不够,CPU 利用率不够高导致的?看了一下现在的配置是
threads 0,32
workers 4
而且就在puma.rb
这个配置文件里面,笔者还发现了这样的代码片段。
before_fork do
require 'puma_worker_killer'
PumaWorkerKiller.config do |config|
config.ram = 5120 # mb
config.frequency = 5 # seconds
config.percent_usage = 0.98
config.rolling_restart_frequency = 6 * 3600 # 12 hours in seconds, or 12.hours if using Rails
config.pre_term = -> (worker) { puts "Worker #{worker.inspect} being killed" }
config.rolling_pre_term = -> (worker) { puts "Worker #{worker.inspect} being killed by rolling restart" }
end
PumaWorkerKiller.start
end
看到这段话的时候
before_fork do
...
config.ram = 5120 # mb
end
真的是茅舍顿开,并不是回流服务多省内存,有这个配置在难怪内存使用一直在 5G ~ 8G 这个区间徘徊。因为回流前期比较穷,笔者都在尽可能节约服务器成本,Ruby 又是出了名吃内存的主,所以引入了PumaWorkerKiller来强行让 puma 的内存使用限制在 5G 左右的水平(当时机器总内存是 8G),适当的时候 kill 掉一些线程来释放内存。
在这些配置的干扰下,客户量上来了,CPU 跟内存却还一直停留在较低水平。更要命的是配置不在代码库中,往往很难察觉到它们的存在。
马上把内存限制扩大到 10G
before_fork do
....
config.ram = 10240 # mb
....
end
Worker 数量也调整大一点,跟 CPU 的核数对应上
threads 0,32
workers 8
经历过两周优化的挫折,笔者已经不再对自己的优化手段报有什么期望了,每次都是期望越大失望越大。然而万万没想到,这次优化效果立竿见影,原来 500ms 的指标一下子就跌到了正常水平了。
我是想过会有改善,就是没想到改善这么明显....当初为了节省费用而编写的配置代码,如今却成了性能瓶颈。后来笔者有把 Worker 数量调整回 4 个,发现单单提升了内存的限制,系统性能也会有所提升,但不够明显,还是要上管齐下才行,看来 worker 数量跟 CPU 的核数一致会是比较合适的选择。
Puma的维护者也建议我们要自己多尝试
Feel free to experiment, but be careful not to set the number of maximum threads to a large number, as you may exhaust resources on the system (or cause contention for the Global VM Lock, when using MRI).
我喜欢这种顶着限速跑的感觉:
人是容易先入为主的动物,特别是在一个地方呆久了,往往会“如入鲍鱼之肆,久而不闻其臭”。以往的性能问题大多都出现在数据库查询中,笔者也下意识地花了很多精力去做数据库调优,却是万万没想到,这次的性能瓶颈出现在平时关注最少的 CPU 跟内存上。
毕竟从指标上看 CPU 跟内存有很大的盈余,却没想到这表面的“盈余”是人为限制的结果。无论是对待 Bug 还是系统性能瓶颈,适当跳出来以局外人的身份审视说不定会有意想不到的效果。这次误打误撞把性能问题解决掉了,也算是运气好吧。