OpCache увеличивает производительность PHP сохраняя уже скомпилированные скрипты PHP в общей памяти, таким образом уменьшая работу для PHP-FPM, которому приходится меньше выполнять загрузку, чтение и обработку PHP при поступлении новых запросов.
Workflow обработки новых запросов выглядит так:
Исходный код проекта – тут>>>.
Содержание
Enable OpCache
Редактируем php.ini
файл, в данном примере это будет /etc/php/7.3/fpm/php.ini
.
Добавляем в блок [opcache]
:
[opcache] opcache.enable = 1
Проверяем синтаксис и перезагружаем конфиги PHP-FPM:
[simterm]
$ php-fpm7.3 -t && systemctl reload php7.3-fpm.service [24-Dec-2019 13:08:08] NOTICE: configuration file /etc/php/7.3/fpm/php-fpm.conf test is successful
[/simterm]
Проверяем phpinfo()
:
Другой вариант – проверить через вызов opcache_get_status()
:
<?php var_dump(opcache_get_status()); //var_dump(opcache_get_configuration()); ?>
Или вывести сразу в JSON:
<?php //var_dump(opcache_get_status()); //var_dump(opcache_get_configuration()); echo json_encode(opcache_get_status()); ?>
Все допустимые функции – тут>>>.
Проверяем:
[simterm]
$ curl -s https://stage.example.com/opcache-status.php | head -n 20 array(8) { ["opcache_enabled"]=> bool(true) ["cache_full"]=> bool(false) ["restart_pending"]=> bool(false) ["restart_in_progress"]=> bool(false) ["memory_usage"]=> array(4) { ["used_memory"]=> int(40959816) ["free_memory"]=> int(93256808) ["wasted_memory"]=> int(1104) ["current_wasted_percentage"]=> float(0.00082254409790039) }
[/simterm]
array(8) { ["opcache_enabled"]=> bool(true)
– окей, активен.
Заодно видим тут память – сейчас обсудим.
Ещё один вариант проверить настройки OpCache – с помощью CLI:
[simterm]
$ php --ri 'Zend OPcache' Zend OPcache Opcode Caching => Disabled Optimization => Disabled SHM Cache => Enabled File Cache => Disabled Directive => Local Value => Master Value opcache.enable => Off => Off opcache.use_cwd => On => On opcache.validate_timestamps => On => On opcache.validate_permission => Off => Off opcache.validate_root => Off => Off opcache.dups_fix => Off => Off opcache.revalidate_path => Off => Off opcache.log_verbosity_level => 1 => 1 opcache.memory_consumption => 128 => 128 opcache.interned_strings_buffer => 8 => 8 opcache.max_accelerated_files => 10000 => 10000 opcache.max_wasted_percentage => 5 => 5 opcache.consistency_checks => 0 => 0 opcache.force_restart_timeout => 180 => 180 opcache.revalidate_freq => 2 => 2 opcache.file_update_protection => 2 => 2 opcache.preferred_memory_model => no value => no value opcache.blacklist_filename => no value => no value opcache.max_file_size => 0 => 0 opcache.protect_memory => 0 => 0 opcache.save_comments => 1 => 1 opcache.optimization_level => 0x7FFEBFFF => 0x7FFEBFFF opcache.opt_debug_level => 0 => 0 opcache.enable_file_override => Off => Off opcache.enable_cli => Off => Off opcache.error_log => no value => no value opcache.restrict_api => no value => no value opcache.lockfile_path => /tmp => /tmp opcache.file_cache => no value => no value opcache.file_cache_only => 0 => 0 opcache.file_cache_consistency_checks => 1 => 1 opcache.huge_code_pages => Off => Off
[/simterm]
OpCache tunning
Все доступные параметры – тут>>>, документация – тут>>>.
OpCache memory
Начнём с памяти.
Возвращаемся к нашему статусу:
[simterm]
root@bttrm-stage-app-2:/home/admin# curl -s https://stage.example.com/opcache-status.php | jq '.memory_usage' { "used_memory": 101210560, "free_memory": 24263968, "wasted_memory": 8743200, "current_wasted_percentage": 6.514191627502441 }
[/simterm]
Тут видим использование выделенной памяти, которой по умолчанию 128 мегабайт, см. opcache.memory_consumption
.
used_memory
– integer; bytes of memory usedfree_memory
– integer; bytes of memory available for cachewasted_memory
– integer; bytes of memory used by invalid or outdated code – OUDATED codecurrent_wasted_percentage
– float; The percentage of wasted opcode cache memory out of total memory available
Wasted memory тут – например, у вас есть код на сервере, который уже закеширован OpCache.
Потом вы задеплоили новый код, OpCache выполнил проверку timestamps, увидел, что скрипт изменился – значит память, выделенная под кеш предыдущей версии скрипта, становится wasted – “зря потраченной“, т.к. кеш уже не валиден.
Значение wasted memory сбрасывается, когда OpCache перезапущен, или когда % wasted паамяти достигает лимита max_wasted_percentage
.
Посчитаем: 101210560 занято кешем сейчас + 24263968 свободной + 8743200 wasted:
[simterm]
root@bttrm-stage-app-2:/home/admin# echo "(101210560+24263968+8743200)/1024/1024" | bc 128
[/simterm]
Наши 128 метров из дефолтных значений, всё сходится.
Избегайте OpCache reset
Проблема заключается в том, что если OpCache забьёт всю доcтупную память (лимит в memory_consumption
) и/или сработает лимит max_wasted_percentage
и/или max_accelerated_files
– то OpCache сбросит весь кеш, и начнёт заполнять его заново.
При этом, даже при условии, что memory_consumption
и max_accelerated_files
будут полностью заняты, но в max_wasted_percentage
лимит ещё не достигнут – OpCache не выполнит reset кеша, и все PHP-скрипты, которые ещё не добавлены в кеш, будут обрабатываться в обычном порядке, т.е. будут каждый раз комплироваться в PHP-FPM.
Кроме того, могут возникнуть проблемы с блокировкиами записи в кеш, т.к. множество запросов к скриптам одновременно начнут зановсить в кеш новые записи об этих скриптах.
Потому – мониторим opcache_get_status()
, и проверяем следующие значения:
- Если
cache_full
, т.е. занята вся память под кеш изmemory_consumption
, но при этом нет “restart_pending” или “restart_in_progress” вopcache_get_status()
– возможно, у вас слишком низкий уровеньmax_wasted_percentage
.
Сравните значенияmax_wasted_percentage
в конфиге иcurrent_wasted_percentage
вopcache_get_status()
Решение: увеличиваем значениеmemory_consumption
- Если
cache_full
== true, а значениеnum_cached_keys
==max_cached_keys
– у вас слишком много файлов в кеше.
При этом если среди них очень мало wasted данных – то резет кеша не будет вызыван, место под новые скрипты в кеше не появится, и новые файлы будут обрабатываться мимо кеширования вообще.
Решение: увеличитьmax_accelerated_files
- Если
cache_full
не появляется никогда, но OpCache постоянно рестартится – то у вас либо слишком много файлов в wasted состоянии в кеше, либоmax_waste_percentage
слишком низкий.
Решение: увеличитьmax_waste_percentage
Проверка рестартов
Самый удобный способ проверить рестарты – через тот же статус:
[simterm]
root@bttrm-stage-app-2:/home/admin# curl -s https://stage.example.com/opcache-status.php | jq '.opcache_statistics' { "num_cached_scripts": 4814, "num_cached_keys": 6386, "max_cached_keys": 16229, "hits": 22609079, "start_time": 1577185705, "last_restart_time": 0, "oom_restarts": 0, "hash_restarts": 0, "manual_restarts": 0, "misses": 5555, "blacklist_misses": 0, "blacklist_miss_ratio": 0, "opcache_hit_rate": 99.97543625954769 }
[/simterm]
Тут:
oom_restarts
: рестарты по достижению лимита памяти –memory_consumption
hash_restarts
: рестарты по достижению лимитовmax_accelerated_files
start_time
: время запуска в UNIX epochlast_restart_time
: время последнего перезапуска в UNIX epoch
Прочие настройки
opcache.use_cwd
Полезно включить, что бы OpCache будет использовать полный путь к файлу скрипта при добавлении его в кеш – это поможет избежать коллизий, когда один и тот же скрипт загружается как единый объект кеша, хотя файлы принадлежат разным проектам на сервере.
Например – у нас целая пачка приложений на Yii, во всех используются аналогичные файлы, т.к. фреймворк один и тот же.
opcache.blacklist-filename
Сюда можно внести например файлы настроек, если они часто изменяются, и любые другие динамически создаваемые-обновляемые файлы.
opcache.revalidate_freq
Как часто в секундах проверять обновились ли файлы. Если задать в 0 – то проверка будет выполняться при каждом запросе к файлу (но см. validate_timestamps
).
opcache.validate_timestamps
Проверяем модицикацию файлов файлов с периодом, заданным в revalidate_freq
.
Если validate_timestamps
задан в ноль, то проверка не будет выполняться вообще – имеет смысл на Production-окружении, т.к. там код будет обновляться редко (но в таком случае требуется либо перепзапуск OpCache при каждом деплое/апдейте файлов, либо выполнять явный вызов reset()
).
opcache.max_accelerated_files
Уже упоминалась, но ещё раз – максимальное кол-во файлов, который OpCache будет хранить. Для лучшей производительности используйте просыте числа – 223, 463, 983, 1979, 3907, 7963, 16229, 32531, 65407, 130987.
В идеале – должно быть больше, чем общее кол-во файлов всех проектов:
[simterm]
root@bttrm-stage-app-2:/home/admin# find /data/projects/ -name *.php -type f | wc -l 206320
[/simterm]
Тогда как лимит по дефолоту задан в 10000, а закешировано сейчас:
[simterm]
root@bttrm-stage-app-2:/home/admin# curl -s https://stage.example.com/opcache-status.php | jq '.opcache_statistics.num_cached_scripts' 2521
[/simterm]
opcache.interned_strings_buffer
PHP использует Interned Strings для хранения данных типа string: для одинаковых строк создаётся одна переменная, которая хранит значение, а во всех остальных случаях – используется указатель (pointer) на эту переменную.
Вместо того, что бы создавать такую переменную для каждого процесса PHP-FPM – OpCache создаёт одну перменную для всех процессов, которые используют одну и ту же строку.
Значение задаётся в МБ, при этом оно будет отнято от значения из opcache.memory_consumption
.
opcache.file_cache
Добавляет кеширование в файл. Помогает при перезагрузке сервера или если вся shared память занята (см. /proc/sys/kernel/shmmax
).
opcache.enable_cli
Включает поддержку кеширования для PHP CLI. Имеет смысл когда, например, часть скриптов запукается из crontab
.
Config summary
Соберём всё вместе:
opcache.memory_consumption=164 // 164 MB памяти под кеш opcache.max_wasted_percentage=5 // при 5% wasted данных - будет выполнен reset всего кеша opcache.max_accelerated_files=7963 // при достижении максимального значения закешированных файлов - будет выполнен reset opcache.use_cwd=1 // использовать полный путь для создания униканого имени ключа opcache.revalidate_freq=0 // проверять не изменился ли файл при каждом запросе, не играет роли при validate_timestamps=0 opcache.validate_timestamps=0 // никогда не проверять изменился ли файл на диске, пока не будет выполнен reset() или перезагрузка сервера opcache.interned_strings_buffer=32 // 32 MB под strings-кеш opcache.file_cache=/data/opcache // файл(ы) кеша храним в /data/opcache opcache.enable_cli=0 // включить OpCache для PHP CLI
OpCache GUI – веб-интерфейс
Для более наглядного представления и работы с OpCache имеется много веб-интерфейсов, например opcache-gui
.
Загружаем файл:
[simterm]
$ wget https://raw.githubusercontent.com/amnuts/opcache-gui/master/index.php
[/simterm]
Копируем его в каталог проекта, что бы получить доступ через NGINX:
[simterm]
$ mv index.php /data/projects/projectname/frontend/web/opcache-gui.php
[/simterm]
Открываем в браузере:
Тестирование и графики
И графики потребления CPU (% Load Average сверху) и RAM (нижний график) на двух серверах API-фронтенда нашего бекенда.
Видим, что Load Average прыгал до 2000% без использования кеша, и всего 300-400% – с активным кешированием.
Аналогично память – 25-50% без кеширования, и 21-35% – с кешированием.
В целом – на этом всё.