PHP’s OPcache and Symlink-based Deploys

Mattias Geniar, Wednesday, December 17, 2014 - last modified: Friday, January 8, 2016

In PHP =< 5.4, there was APC. And in APC, there was the apc.stat option to check the timestamps on files to determine if they've changed, and a new version should be cached. However, in PHP 5.5+, it introduces OPCode Caching as an alternative to APC, and that works a bit differently.

Now, with PHP 5.5 and the new OPcache things are a bit different. OPcache is not inode-based so we can't use the same trick [symlink-changes for deploys].
Rasmus Lerdorf

In short: if the "path" to your document root or PHP doesn't actually change, PHP's OPcache will not reload your PHP code.

Flush the PHP opcache

So if your PHP code is in the following directory structure, and the current symlink is changed to point to a new release directory, the OPCache won't read the new file since the path of the file remains the same.

$ ls
current -> releases/20141217084854
releases/20141217084854
releases/20141217085023

If the current symlink changes from releases/20141217084854 to releases/20141217085023, you need to manually clear the OPCache to have it load the new PHP files.

There is no mechanisme (yet?) in OPCache that allows you to stat the source files and check for changes. There are options like opcache.validate_timestamps and opcache.revalidate_freq that allow you to periodically check for changes, but that's always time-based.

So to clear the OPCache after each deploy, you now have two options:

  1. Reload or restart the PHP-FPM daemon (you'll need sudo-rights for this)
  2. Use a tool like cachetool to flush the OPCache via the CLI

This is something to keep in mind.

Nginx fix: use $real_path instead

If you're using Nginx as the webserver in front of your PHP there is also a configuration change you can make to let nginx resolve the symlinks. The result is that PHP does not see the symlink, but it's docroot is shown as a directory that points to releases/20141217084854.

In your Nginx configuration, you probably have a configuration directive that looks like this.

    fastcgi_index           index.php;
    fastcgi_param           SCRIPT_FILENAME  $document_root$fastcgi_script_name;
    fastcgi_param           DOCUMENT_ROOT $document_root;

The keypoint here is $document_root in the fastcgi_param. That points to the docroot of Nginx, usually /var/www/vhosts/site.tld/current.

If you change the Nginx configuration to use $realpath_root instead of $document_root, Nginx will resolve the symlink and will let that variable refer to the full path to your deploy, being /var/www/vhosts/site.tld/releases/20141217084854.

So the entire snippet looks like this:

    fastcgi_index           index.php;
    fastcgi_param           SCRIPT_FILENAME  $realpath_root$fastcgi_script_name;
    fastcgi_param           DOCUMENT_ROOT $realpath_root;

This may introduce other bugs in your code (such as a change in the $_SERVER variables in PHP or ../../-style symlinks that won't refer to the same path again), but it will also solve your PHP OPcache troubles.



Hi! My name is Mattias Geniar. I'm a Support Manager at Nucleus Hosting in Belgium, a general web geek & public speaker. Currently working on DNS Spy & Oh Dear!. Follow me on Twitter as @mattiasgeniar.

Share this post

Did you like this post? Will you help me share it on social media? Thanks!

Comments

Jrean Monday, June 13, 2016 at 13:29 - Reply

Hi,
Thank you very much for this note! I spent several hours trying to deal with that…
I can’t see any publishing date for this post…
I tried to switch “$document_root” with “$realpath_root”.
I’m wondering if this is still the best practice?

Thanks!


Leave a Reply

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

Inbound links