Node.js对小缓冲区的需求很大

我已经讨论过用于监视PostgreSQL查询服务,为此我们实现了一个在线服务器日志收集器,其主要任务是一次同时接收来自大量主机的日志流,快速将其解析为行,然后根据某些规则将其分组,进行处理和写入导致PostgreSQL存储。



在我们的案例中,我们谈论的是数百台服务器以及数百万的请求和计划,每天生成超过100GB的日志。因此,当我们发现最大的资源份额恰好用于这两个操作时,就不足为奇了:解析成行并写入数据库。

我们深入探查器的内容,发现使用BufferNode.js的一些功能,这些知识可以大大节省您的时间和服务器资源。

CPU负载




处理器的大部分时间都花在处理传入的日志流上,这是可以理解的。但什么是不明确的是原始的资源密集性的二进制块的输入流的“切片”成线\r\n:该



细心的开发商会马上注意到一个不那么有效字节周期通过输入缓冲器。好吧,由于该行可以在相邻块之间“撕裂”,因此从先前处理的块中剩下“尾部附着”功能

尝试阅读热线


可用的解决方案的快速回顾把我们带到正规的readline模块正好与必要的功能进行切片到行:



它实施后,“切片”,从事件探查器的顶部去更深:



但是,事实证明,readline的力量字符串为UTF-8内部,这是不可能的如果日志条目(请求,计划,错误文本)具有不同的源编码,则执行此操作。

的确,即使在一个PostgreSQL服务器上,也可以同时激活多个数据库,每个数据库都以其原始编码精确地生成输出到公共服务器日志结果,win-1251上的数据库的所有者(有时,如果不需要“诚实的”多字节UNICODE可以使用它来节省磁盘空间,这很方便)可以使用几乎相同的表和索引“ Russian”名称来观察其计划:



修改自行车


这很麻烦……您仍然需要自己裁剪,但是使用类型的优化Buffer.indexOf()而不是“字节扫描”:



似乎一切都很好,测试电路中的负载没有增加,win1251名称得到修复,我们投入了战斗……塔达姆!CPU使用率周期性地突破100%的上限



怎么回事?..事实证明,这是我们的“错误Buffer.concat”,我们“粘住了上一个块中剩下的尾巴”:



但是,只有当我们在该块中通过一条线时,我们才会产生粘连,但它们不应有很多-真的,真的吗?直到现在,有时才出现数百个16KB段的“字符串”



感谢其他认真开发此功能的开发人员。它“很少但准确”地发生,因此不可能在测试电路中预先看到。

显然,将数百兆字节的小片段粘贴到缓冲区数百次是通往内存重新分配深渊的直接路径,因为我们观察到了CPU资源的消耗。因此,让我们在行完全结束之前不要粘上它。我们只是把“碎片”到一个数组,直至现在是时候给整个行“走出去”:



现在负载已经回到了readline的指标。

内存消耗


许多使用动态内存分配语言编写的人都知道,最令人不快的``性能杀手''之一是垃圾收集器(GC)后台活动,该活动会扫描内存中创建的对象并删除较大的对象不需要任何人。这个麻烦也使我们不胜枚举-从某个时候开始,我们开始注意到GC活动某种程度上太多了,而且不合适。



传统的“扭曲”并没有真正的帮助。民间的智慧并没有让我们失望-我们看到了8360字节的Buffer云,总大小为520MB ...



并且它们是在CopyBinaryStream内部生成的-情况开始有所改善...

从STDIN和BINARY复制...


为了减少传输到数据库的流量,我们使用二进制格式COPY实际上,对于每条记录,您都需要向流发送一个缓冲区,该缓冲区由``片段''组成-记录中的字段数(2个字节),然后是每列的值的二进制表示形式(每种类型ID +数据4个字节)。

由于表的这一行几乎总是具有可变的“累加”长度,因此,立即分配固定长度缓冲区不是一种选择;如果缺少大小,则重新分配将很容易“消耗”性能,并且已经变得更高了。因此,使用来“逐块胶合”也是值得的Buffer.concat()

备忘录


好吧,由于我们有很多遍又一遍地重复(例如,同一张表的记录中的字段数),因此,让我们记住它们,然后取入在第一次调用时生成的现成字段基于选项的COPY格式,有很多选项-典型的片段以1、2或4个字节出现:



而且... bam,耙子到了!



也就是说,是的,每次创建缓冲区时,默认情况下都会分配8KB的内存,以便可以将一行中创建的小型缓冲区“旁边”堆叠在已分配的内存中。我们的分配工作是“按需”的,结果根本不是“接近”-这就是为什么我们的1-2-4字节缓冲区中的每个物理占用8KB +标头 -这就是我们的520MB!

智能备忘录


嗯……为什么我们还要等到需要这个或那个1/2字节的缓冲区?4字节是一个单独的问题,但是其中一些不同的选项总共为256 +65536。因此,让nagenerim 一次全部同时,我们减少了每个检查存在的条件-它也将更快地工作,因为初始化仅在过程开始时进行。



也就是说,除了1/2字节的缓冲区外,我们立即为4字节的值初始化了最运行的值(低2个字节和-1)。而且-它帮助了,只有10MB而不是520MB!


All Articles