水管工程序员,或者一个泄漏的故事以及处理它的困难

是2月25日星期二。很难在2月22日星期六发布该版本。似乎所有最坏的情况都已过去,没有任何预示着麻烦。但是,当监视错误导致访问控制服务的协调器进程发生内存泄漏时,一切都一次改变了。

从哪里来?协调员代码库的最后主要更改是在两个月前的以前版本中,此后,内存没有发生任何明显变化。但是,不幸的是,监控计划很严格-协调员的记忆显然开始泄漏到某个地方,服务层上标榜着一个大水坑,这意味着水暖团队有很多工作要做。



首先,我们做一个小题外话。除其他外,VLSI允许您跟踪工作时间并通过脸部模型,指纹或访问卡监控访问情况同时,VLSI与端点控制器(锁,旋转门,访问终端等)进行通信。单独的服务与设备通信。它是被动的,基于访问控制设备通过HTTP(S)实现的自己的协议进行交互。它是根据我们公司服务的标准堆栈编写的:PostgreSQL数据库,Python 3用于业务逻辑,并通过我们平台的C / C ++方法进行了扩展。

一个典型的Web服务节点由以下过程组成:

  • 监控是根本过程。
  • 协调器是监视器的子进程。
  • 工作流程。

Monitor是第一个Web服务过程。它的任务是调用fork(),启动子协调器进程并监视其工作。协调器是Web服务的主要过程,正是他从外部设备接收请求,发送响应并平衡负载。协调器将请求发送到工作流程以执行,他们将其执行,将响应传输到共享内存,并通知协调器任务已完成,您可以提取结果。

谁该怪谁该怎么办?


因此,访问控制服务的协调器与我们公司中其他服务的协调器的不同之处在于存在Web服务器。其他服务的协调员没有泄漏,因此必须在我们的配置中寻找问题。更糟糕的是,在测试台上,新版本存在了很长时间,没有人注意到它们上的内存问题。他们开始更加仔细地观察,发现内存仅在其中一个机架上流动,即使如此,也取得了不同的成功-这意味着问题仍然不那么容易重现。



该怎么办?如何找到原因?首先,我们进行了内存转储,并将其从平台发送给专家进行分析。结果-转储中没有任何内容:没有理由,也没有暗示朝哪个方向看。我们检查了先前版​​本中协调器代码的更改-突然我们做了一些糟糕的编辑,但是只是不立即理解?但是,没有-在协调器代码中仅添加了一些注释,但是将一些方法移到了新文件中-总的来说,这没有什么犯罪。

他们开始将目光投向我们的同事-我们服务核心的开发人员。他们满怀信心地否认有可能卷入我们的不幸之中,但愿意将tracemalloc监视植入服务中。言归正传,在下一个修补程序中,我们将完成服务的确定,快速测试,发布战斗。

我们看到了什么?现在,我们的记忆不仅迅速消失,而且很快消失-增长已成倍增长。我们将第一个高峰归因于邪恶力量及其伴随的因素,但是第二个高峰(在第一个高峰之后的几个小时)清楚地表明,等待下一个具有此服务紧急状态的修补程序发布的时间过长。因此,我们采用tracemalloc的结果并对服务进行修补,并通过监视回滚编辑以至少返回线性增长。



看来我们已经在Python分配的内存上获得了tracemalloc工作的结果,现在我们将对其进行调查,找出泄漏的根源,但事实并非如此-在收集的数据中,我们没有在监视图上看到5.5GB的峰值。最大使用的内存只有250MB,甚至traceMalloc也吃掉了130MB。这在某种程度上是可以解释的-tracemalloc允许您查看Python中的内存动态,但它不知道由我们的平台实现的C和C ++包中的内存分配。无法在获取的数据中找到有趣的东西,将内存按可接受的容量分配给诸如流,字符串和字典之类的普通对象-一般来说,没有可疑之处。然后,我们决定从数据中删除所有多余的东西,只保留总的内存消耗和时间,并进行可视化处理。尽管可视化并不能帮助回答“正在发生什么”和“为什么发生”的问题,但借助它的帮助,我们看到了与监视数据相关的信息-这意味着我们肯定在某处存在问题,需要寻找它。



那时,我们的水管工团队在寻找泄漏点的想法上一无所有。幸运的是,这只鸟向我们唱歌说,确实发生了平台的一项重大更改-Python版本从3.4更改为3.7,这是一个巨大的搜索领域。

我们决定在Internet上寻找与Python 3.7中的内存泄漏相关的问题,因为可以肯定有人已经遇到了这种现象。不过,Python 3.7是很久以前发布的,我们仅在当前更新时才切换到它。幸运的是,很快就找到了我们问题答案,并且还存在问题拉动请求来解决该问题,而她本人也处于Python开发人员所做的更改中。
发生了什么?

从3.7版开始,ThreadingMixIn类的行为已更改,我们从该类继承自Web服务器,以在单独的线程中处理每个请求。在ThreadingMixIn类中,他们将所有创建的线程的条目添加到数组中。由于此类更改,处理设备连接的类实例在完成后不会释放,并且Python中的垃圾收集器无法从已用线程中清除内存。这就是导致分配的内存与对我们服务器的请求数量成正比的线性增长的原因。

就是这里,Python模块的阴险代码有一个大洞(Python 3.5中的代码在更改前显示在左侧,右侧在3.7中显示在更改之后):



找出原因,我们很容易地消除了漏洞:在我们的继承人类中,我们更改了返回旧行为的标志的值,仅此而已-胜利!流是像以前一样创建的,无需写入class变量,但是我们在监视图上观察到了令人愉快的画面-漏洞已得到修复!



胜利后写这件事真是太好了。切换到Python 3.7后,我们可能不是第一个遇到此问题的人,但很可能不是最后一个。对于我们自己,我们得出结论,我们需要:

  • 采取更严肃的方法来评估重大变更的可能后果,尤其是如果其他应用决策取决于我们。
  • 如果平台发生全局更改(例如,更改Python版本),请检查代码中是否存在问题。
  • 应对不仅是作战服役,而且还包括测试服役的监视时间表中的任何可疑更改。尽管有当前的垃圾收集器,但Python中也存在内存泄漏。
  • 需要谨慎使用诸如tracemalloc之类的内存分析工具,因为错误使用它们会使情况变得更糟。
  • 您需要为以下事实做好准备:检测内存泄漏需要耐心,恒心和一些侦探工作。

好吧,我要感谢所有帮助应付紧急水管工作并再次恢复其以前的工作能力来为我们服务的人!

All Articles