可遇不可求的经历,月更素材不请自来
0x00.前言
早上被前辈敲门叫醒,一看手机客户在wx
群里说服务器SSH
登不上去,叫我们赶紧过去
0x01.问题确认
去了一看SSH
是可以登录的,只不过服务端回显特别慢,凭经验看有点像内存或硬盘爆满的现象
因为速度实在是太慢了于是放弃SSH
,登录到CIMC
(相当于VNC
)的界面,一看一整页的OOM Killer
已经开始制裁(第一个是java
,后面的都是python3
那么问题已经确认,是系统发生了OOM
,路上还在各种猜测比如是硬盘炸了之类的……emmm
,想了一下可能是自己昨天从多线程切换到了多进程所导致的,进入top
再按C键,然后一整屏的python3 collector.py
,粗略估算首屏就有二三十个了
而且所占内存巨大,物理机128G
内存,平常基本浮动在64G
左右,这时候free -m
的整机内存所剩无几
下注:现网真实复现数据
1 | [root@node1 ]# free -m |
0x02.恢复服务
也很纳闷,早在前一天下午就讨论到了使用多进程有OOM
风险,也只开了max_workers
为4
的进程池,怎么就会爆内存了呢?
但是首先还是要恢复服务,把ps -ef
出的collector
进程全都kill
掉,并且用supervisorctl
把对应的子服务关闭,内存算是回到了正常水平
然后就是各种第三方组件的恢复,最后Kafka
一直启动不能,messages
里也一直有error
报错,临时删掉data
再启动也终于恢复了
0x03.等待复现
这时候还没换回多线程,准备先跑着看看会不会还有问题……结果果不其然,下午做完核酸检测回来的时候前辈说复现了,现场还在,正准备开始kill
操作巨卡无比已是在意料之中,上来就free -m
一看果然内存又基本跑满,再通过top
看有非常多的python3
,都得翻到第二页才能看完
虽然没来得及手动kill
,不过赶巧等了一会儿这些进程都结束了,翻日志发现刚刚写入了一个含7850484
条记录的索引,耗时490.36
秒
先不说其他的,7850484
应该是遇到的每小时级别最大的键了,其实这个键的写入操作本来是应该在半夜3
点后的定时任务里去完成的
结果半夜直接OOM
一直没有完成,拖到了下午3
点又开始跑定时任务的时候就复现了
0x04.溯源分析
是时候贴一下关键函数的源码了,这一个函数里就写了俩bug
简直了
1 | def bulk_load_to_es_by_pool_executor(df_content, timestamp, index_name_type): |
1.content
过大
没错,content
就是那个含有7850484
条记录的bulk
的请求体,粗略估测不超过15MB * 66 = 990MB
,也就是接近1G
的大小
而创建ProcessPoolExecutor()
时content
变量已经存在,multiprocessing
启动进程的方法在Unix
中的默认值是fork
也就是说fork
出来的每一个进程都会拷贝一份content
变量,造成不必要的内存开销
2.未限制max_workers
翻阅了一下gitlab
的历史,可以看出最开始用的ThreadPoolExecutor
是有显式设定max_workers
的,但是ProcessPoolExecutor
却没有传入max_workers
想了下原因是,当初看到文档max_workers
默认是机器的处理器个数,想着应该不需要手动设定数量吧,所以到头来代码里就一直没有给多进程设置max_workers
的地方……lab
里测试环境的CPU
是16
再不就是32
,而现网环境是直接把CentOS
装到了物理机上,也没有ESXi
之类的虚拟化平台,CPU
就是64
……
以上两个原因,导致在处理7850484
条记录时占用接近64G
的内存,加上平常基本浮动在64G
左右,合计就接近128G
半夜的时候超过128G
导致OOM
,而次日下午是刚恢复完其他服务没有占用过多内存才勉强运行完成
0x05.代码重构
1.content
过大
未完待续……
2.限制max_workers
executor = ProcessPoolExecutor(max_workers=int(thread_pool_max_workers))
0x06.后记
现网OOM
有被吓到,本来都订了当晚回来的机票,被迫取消留用晚了一天才回来(