记一次Excel导出导致内存耗尽的问题
今天遇到一个问题,导出线上2个月的Excel统计数据频繁导致报错,一查php-fpm日志,则是提示:
PHP Fatal error: Allowed memory size of 298844160 bytes exhausted (tried to allocate 262144 bytes)
一开始首先想到是Excel导出时很吃内存,数据一多就容易将内存耗尽,于是先从代码上做优化,取完数据后将无用的数组全部unset()掉。
继续去预发布环境(全量数据)尝试,发现还是error错误,于是继续排查错误,发现还是上面的报错,只不过这次是ORM提示的。然后赶紧检查ORM的写法,果然是很低效的写法:一是关联了两张无用表,二是取了全量的数据(检查发现包括一个很大的text类型字段)。于是赶紧优化,删除无用的关联表,然后将select(*)改成select(a,b,c)特定的字段。再去预发布环境测试,终于没有再报内存耗尽的错误了……那就耐心等待数据处理和下载吧……
结果意想不到的问题,是php-fpm报"time out"错误,于是再将php-fpm超时处理修改下:
ini_set(max_execution_time, 300); ini_set(memory_limit, 285M);
然后再做尝试,继续等待……结果又是nginx报504错误……真是按下葫芦浮起瓢,再去nginx上添加参数:
... fastcgi_read_timeout 300; ...
再尝试后,期待已久的nginx终于被下载下来……
总结下,今天遇到问题的原因——
-
使用过ORM导致太多无用字段被加载,这是内存泄露的原因之一 从DB取回的大量的数据做计算,数组没有及时unset,这是内存泄露的原因之二 ini_set()函数起初只将内存设置为128m,不够支撑取出的数据,这是内存泄露的原因之三 ini_set()函数直将php-fpm进程的处理时间设为55s,不够数据处理的时间,这是超时原因之一 nginx的fastcgi_read_time_out时间默认为60s,这是超时的原因之二
下载下来的数据无非也就7000条……但是暴露的问题却很多。
大量数据的处理和平时CURD是很不相同的场景,这种情形下对内存和CPU都有极大的考验。我们习惯了ORM的方便却往往忽略它带来的性能损耗。ORM容易取出全量字段,在数据量大时容易占用太多内存,同时ORM会在取出数据后做额外封装,这又是一笔额外的开销,数据量小时看不出来,但是数据量大时就很要命。因此在处理数据量的请求时慎用ORM,而是采用比较贴近原生SQL的类似QueryBuilder的写法,后者在运行效率和开发效率中取一个平衡点。
此外,类似于下载文件等耗时较长的请求,对各种超时也是有要求的。不管是nginx还是php-fpm,对超时的默认限制都不高,nginx的proxy_read_timeout默认是60s,对长时处理这显然太短了……因此对各种超时的处理也要格外注意。
