PHP OPcache important settings and revalidation simplified

About OPcache :

PHP as a language is an interpreted. But the interpretation happens from the binaries. These binaries are compiled into intermediate bytecodes. OPcache comes into picture during the compilation phase where it stores the precompiled script's bytecodes into a shared memory. When next time PHP needs to compile the same PHP script, the precompile bytecodes from the shared memory are used.. Therefore removing the requirement for PHP to load and parse scripts on each request. As a result improving overall PHP performance.

The OPcache extension is bundled with PHP 5.5.0 and later versions.

Why it is needed :

Okay wait... Let's really get into a very vital need of this OPcache in the ever growing world of PHP frameworks. Any PHP framework has some core package files which we never change. These php package files really let us use the framework capabilities. For example for laravel, we have illuminate's laraval packages. Now on top of that, we install new PHP packages as and when needed using dependency managers like composer. We hardly change these files unless we update/install/remove a package dependency.

Now, independent of which PHP framework you are using, these core PHP script files which constitute framework more or less are compiled for each request to generate their bytecodes for interpretation. Similarly, the dependency package PHP script are compied as per their need for that particular request.

Why do we really need to compile these core PHP script files which we indeed hardly change?? Isn't that an overly unneccessary overhead for PHP compilation process to compile these hundreds of PHP script files on each request and thenm intrepret them? Doesn't make much sense right? There you go.. OPcache to the rescue...

Important settings of OPcache :

As OPcache overall sounds really fascinating which internally optimizes the compilation process of PHP scripts, it is equally important to know the important settings. Cache handling, cache clearing and cache storing and overall invalidation depends on these settings.

  • Finding the php.ini to work with : You need to find the php.ini which is currently in use. There are different ways to find it. You can find it from terminal using :
php --ini | grep -i loaded

OR create a temporary script file called 'info.php` and have following content on it :

phpinfo();

Once you open this file into browser, you would be able to see which php.ini is used.

Mostly if you have php version 7.x with fpm installed then it will be inside /etc/php/7.x/fpm/php.ini. Or if you have 7.x with apache2 installed then it will be inside /etc/php/7.x/apache2/php.ini.

  • Enable opcache :

    In the loaded and used php.ini, you can enable and disable opcache by updating following directive :

    opcache.enable=1 //Enables OPcache
    opcache.enable=0 //Disables OPcache
    

    OPCache also can be enabled for CLI, PHP running from the command line interface(terminal) :

    opcache.enable_cli=1 //Enables OPcache
    opcache.enable_cli=0 //Disables OPcache
    
  • OPcache RAM usage :

    When the entire cache is stored into shared memory, the default max size of that memory storage is 64MB before PHP 7.0.0 and 128MB for after versions. You can update it as per your memory. Ideally 64MB to 128MB size is more than enough for most of the PHP frameworks. In the loaded and used php.ini, you can change this setting by updating following directive :

    opcache.memory_consumption=128
    
  • Cached script limit :

    By default OPcache can cache 2000 files before PHP 7.0.0 and 10000 for after versions. You can increase this number as per your requirement. For frameworks like PHP Magento you could probably have this value bumped up as the core files are large in number.

    First know count of PHP scripting files in your codebase :

    # Make sure you change this path as per your setup
    cd  /var/www/html 
    # Count number of php files
    find . -type f -print | grep php | wc -l
    

    Now above will givbe you a numeric count of total PHP script files in your codebase. Now you can set OPcache setting accordingly. In the loaded and used php.ini, you can change this setting by updating following directive :

    opcache.max_accelerated_files=20000
    
  • Set automated cache clearing :

    When you have new updates in cached PHP script files, OPcache will attempt to clear and then revalidate their cache. This is set using validate_timestamps directive and it happens periodically based on other directive setting revalidate_freq. When this directive is disabled, you MUST reset OPcache manually via opcache_reset(), opcache_invalidate() or by restarting the Web server for changes to the filesystem to take effect. In the loaded and used php.ini, you can change this setting by updating following directive :

    opcache.validate_timestamps=1 //Enabled
    opcache.validate_timestamps=0 //Disabled
    
  • Revalidation and it's frequency :

    OPcache revalidates the opcache afer certain number of seconds to check if your code has changed. 0 value signifies that it checks your PHP code every single request which is unneccessary. By default it happens every 2 seconds. If you have PHP files changing hardly on your setup, it makes sence to increase the frequency duration. As revalidation also takes memory, bumping this up might save unneccessary memory revalidation usage. In the loaded and used php.ini, you can change this setting by updating following directive :

    opcache.revalidate_freq=120
    

    OR you can totally disable this revalidation by updating following directive :

    opcache.validate_timestamps=0 //Disables OPcache revalidation
    

    In your development or sandbox environment you can set this to 0 so that changes take effect immediately. However, it is as good as disabling which can save lot of syscalls.

  • Skipping caching selecively :

    If you know that let's say user.php file in your framework changes often and you do not really want to clear cache everytime it changes. You can basically have OPcache ignore the caching of this file. It is called blacklisting file in terms of OPcache. This setting is littlbit different. It takes an absolute path location of a .txt file. In this text file you can specify the files you want to blacklist(skip) for caching. In the loaded and used php.ini, you can change this setting by updating following directive :

    opcache.blacklist_filename=/etc/php/7.x/fpm/opcache_blacklist.txt
    

    You can have whatever name you want for this text file. And then inside opcache_blacklist.txt :

    #Specific file inside /var/www/html/
    /var/www/html/user.php 
    
    #All files starting with user* inside /var/www/html/
    /var/www/html/user
    
    #Wildcare usage inside /var/www/html/
    /var/www/*-user.php
    
  • Increasing request response shutdown :

    PHP OPcache uses a mechanism which checks for repetitive occurances of strings and internally stores single value of it with remaining ones having pointer to the same value. The fascinating thing is instead of having a pool of these strings for each and every FPM process, this shares it across all of your FPM processes.

    In OPcache settings, you can control how much memory this process can use from directive opcache.interned_strings_buffer Its default value is 4(MB) before PHP 7.0.0 and 8(MB) for after versions.

    In the loaded and used php.ini, you can change this setting by updating following directive :

    opcache.interned_strings_buffer=12
    

Note that to have changes to take effect from php.ini, you need to restart PHP FPM or apache2 depending on your setup.

Clearing the OPcache :

When OPcache is enabled, any changes in cached PHP script files will not take effect until OPcache is cleared or it is revalidated. This is applicable when you release new updates into a OPcache enabled PHP server.

To clear cache there are multiple ways :

  • Clearing from browser :

    You can have a file opcache_refresh.php with following content :

    try{
        opcache_reset();
        echo "OPcache has been cleared successfully!";
    }
    catch(\Exception $e){
        echo "Oops.. OPcache could not be cleared!";
    }
    

    When you request this file from browser, it will reset(clear) the opcache and any new changes done inside the cached files will start reflecting.

  • Clearing from terminal :

    If you have nginx with PHP FPM :

    sudo service php7.x-fpm reload
    

    If you have apache2 :

    sudo service apache2 reload
    
  • Using cachetool :

    This is very useful when you do not want to have an open PHP file to reset opcache and you do not want to reload FPM or apache2 for the same. cachetool is a way between the two :

    To Install :

    // Download the phar
    curl -sO http://gordalina.github.io/cachetool/downloads/cachetool.phar
    // Set permissions
    chmod +x cachetool.phar
    

    Now you can clear opcache using following command :

    // Using an automatically guessed fastcgi server
    php cachetool.phar opcache:reset --fcgi
    
    // Using php fpm socket
    php cachetool.phar opcache:reset --fcgi=/var/run/php7.x-fpm.sock
    

OPcache and the automated deployments :

When you have automated deployments set up on your PHP server, you need to incorporate OPcache clearing so that new changes take effect.

If you do not perform this manually, eventually OPcache will revalidate itself based on your OPcache revalidation settings assuming you have validate_timestamps set to 1. But it is pretty handy to do it manually as part of your automated deployment scripts to make sure changes take immediate effect.

From my personal experience, relading FPM or apache2 to clear OPcache is not a good option. Even if it gracefully reloads PHP processes, any request which is(unfortunarely) ongoing on the server gives 502 bad gateway. I would rather use the alternative like cachetool mentioned in above section which does not affect any ongoing PHP processes at all.

The sequence when you reload OPcache matters. As soon as git or any of your deployment methods pulls the latest PHP code changes, you should first clear the OPcache. Then you can run all the remaining deployment commands which may use the updated PHP files. This makes sure the deployment commands do not use cached PHP script opcodes even when the sever has pulled the latest changes.