Bitrix, opcache и утечки памяти

Появилась задача разобраться, отчего некий самописный компонент битрикса, где в цикле выполняется охренеллиард однотипных запросов к базе данных, после обновления PHP с 7.1 на 7.2 внезапно начал падать то в пустой экран, то в классический Fatal error: Allowed memory size of … bytes exhausted.

После изучения означенного вопроса оказалось, что суть проблемы сводится к битриксовому API CIBlockElement::GetList() и соответствующему ему GetNext(), которые при некоторых условиях переставали высвобождать память после каждого своего вызова. Очевидно, что после попытки выполнения их по нескольку десятков тысяч раз внутри одного скрипта (который по расписанию перегенерирует карту сайта), разрешенный объем памяти в PHP довольно быстро заканчивался.

Тот факт, что ранее тот же самый код работал без подобных проблем, со временем вывел расследование на opcache, а конкретно – к оптимизации ZEND_OPTIMIZER_PASS_6 (1<<5) /* DFA based optimization */. По счастью, настройки opcache позволяют выбирать активные оптимизации и отключать те, что могут вызывать проблемы. Соответственно, в конфигурацию была добавлена следующая опция, отключающая только проблемную оптимизацию и оставляющая все остальные:

opcache.optimization_level=0x7FFEBFDF

Понятно, что это могло привести к какой-то деградации производительности в opcache, но при выполнении означенных ранее циклов полностью перестала утекать память.

Для справки, полный список опциональных оптимизаций:

#define ZEND_OPTIMIZER_PASS_1		(1<<0)   /* CSE, STRING construction     */
#define ZEND_OPTIMIZER_PASS_2		(1<<1)   /* Constant conversion and jumps */
#define ZEND_OPTIMIZER_PASS_3		(1<<2)   /* ++, +=, series of jumps      */
#define ZEND_OPTIMIZER_PASS_4		(1<<3)   /* INIT_FCALL_BY_NAME -> DO_FCALL */
#define ZEND_OPTIMIZER_PASS_5		(1<<4)   /* CFG based optimization       */
#define ZEND_OPTIMIZER_PASS_6		(1<<5)   /* DFA based optimization       */
#define ZEND_OPTIMIZER_PASS_7		(1<<6)   /* CALL GRAPH optimization      */
#define ZEND_OPTIMIZER_PASS_8		(1<<7)   /* SCCP (constant propagation)  */
#define ZEND_OPTIMIZER_PASS_9		(1<<8)   /* TMP VAR usage                */
#define ZEND_OPTIMIZER_PASS_10		(1<<9)   /* NOP removal                 */
#define ZEND_OPTIMIZER_PASS_11		(1<<10)  /* Merge equal constants       */
#define ZEND_OPTIMIZER_PASS_12		(1<<11)  /* Adjust used stack           */
#define ZEND_OPTIMIZER_PASS_13		(1<<12)  /* Remove unused variables     */
#define ZEND_OPTIMIZER_PASS_14		(1<<13)  /* DCE (dead code elimination) */
#define ZEND_OPTIMIZER_PASS_15		(1<<14)  /* (unsafe) Collect constants */
#define ZEND_OPTIMIZER_PASS_16		(1<<15)  /* Inline functions */

#define ZEND_OPTIMIZER_IGNORE_OVERLOADING	(1<<16)  /* (unsafe) Ignore possibility of operator overloading */

PS: Всё это не было бы особо мозголомно, если бы не тот факт, что вся эта проблема наблюдалась на одном сайте и не наблюдалась на другом – в разных пулах PHP-FPM в рамках одного и того же сервера. Объяснение тому оказалось прямолинейным: там, где проблема не наблюдалась, opcache отчего-то не функционировал. Нет, не был выключен в настройках – он был включен, но просто перестал работать. После перезапуска FPM проблема начала одинаково воспроизводиться на обоих сайтах. Впоследствии, к слову, это же вынудило переустановить PHP из репозитория Remi, раз в официальном и версия PHP получала лишь обновления безопасности к старому релизу, а не обновления к более новым, и происходили подобные вещи. К сожалению, начальную проблему с DFA это обновление не исправило.

This entry was posted in Софт and tagged , . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>