OPcache Overview0:00
Hello again. In this final lesson, we are going to explore an important php configuration that allows us to optimize our applications in production. What we have so far is a setup in which Nginx routes requests to php-fpm. And php-fpm starts multiple php processes that handle those requests. And when a php process receives a request, the source code is then compiled into something called opcode. These are instructions that the PHP runtime can understand and execute. To avoid the need to recompile PHP scripts on every single request, the OPcache PHP extension was introduced. And using OPcache, the compiled opcode will be stored in a shared memory space that is used by all php processes. So the very first request will hit a cold start in which the opcode is generated and stored in memory. And then subsequent requests will reuse this generated opcode and skip the
Enabling OPcache Settings0:58
the very first request will hit a cold start in which the OPcode is generated and stored in memory. And then subsequent requests will reuse this generated OPcode and skip the compilation part. Let's look at how to enable and configure OPcache. We will need to edit the php.ini configuration file as root. So let's run sudo nano /etc/php/8.1/fpm/php.ini. And inside this file, let's search for the OPcache table. So OPcache. And here we go. Let's start with uncommenting this line. This will enable OPcache in our PHP setup. And next, let's look at this OPcache memory consumption attribute. This one configures the maximum memory size OPcache is allowed to use for storage of OPcode. The size needs to be large enough to store compiled OPcode for all PHP scripts on our VPS, but not too large that it keeps us from using available memory on the server for other purposes. We
to be large enough to store compiled OB code for all PHP scripts on our VBS, but not too large that it keeps us from using available memory on the server for other purposes. We can monitor the status of OB cache by checking the output of the phpinfo method or a different method that I'm going to show you in a bit. Now let's move to the OB cache interned strings buffer. This one decides the size of the OB cache memory we want to dedicate for duplicated strings. OB cache stores literal strings, class names, and a few other samples in the cache once so that the memory is allocated just one time. By monitoring and configuring the interned strings buffer, we can save OB cache memory and increase efficiency of string comparison operations. And next, we have the OB cache maximum accelerated files. This attribute configures the number of PHP script files to store in OB cache. Let's say you
comparison operations. And next, we have the opcache.max_accelerated_files attribute. This attribute configures the number of PHP script files to store in OPcache. Let's say you have two Laravel applications on the same VPS. The PHP files in the vendor directory of these two applications will be cached separately. So we need to make sure that the value of the max_accelerated_files attribute covers the total number of PHP files on our server. Now let's move to a different attribute. Let's uncomment this opcache.validate_timestamps attribute and set it to zero. So disabling timestamp validations stops OPcache from checking the file's timestamp by inspecting the file system on our VPS. Checking the timestamp adds an overhead that we want to avoid in production. It is useful in development because OPcache will be reset when the content of our scripts change by checking the timestamp,
Testing Cache Behavior4:07
adds an overhead that we want to avoid in production. It is useful in development because OPcache will be reset when the content of our scripts change by checking the timestamp, which allows us to see changes to our code as they happen. We need that in development, in production, we don't need that. So we avoid the overhead. Moving on, let's go to the OPcache save comments attribute, disable it and uncomment. Disabling this one configures OPcache to drop PHP documents from the compiled code, which reduces the size. Now let's save the file and restart PHP-FPM. So sudo service php8.1-fpm restart. Now let's go check our site, go with php artisan serve. And here we go. It's working. Let's go back to php artisan route:edit, edit the route, and add a dump of the output of this method. So dump(opcache_get_status()). Now let's deploy, look out of the server and then push to git. Then we go to GitHub Actions.
the route and add a dump of the output of this method. So dump ob_cache_get_status. Now let's deploy, look out of the server and then push to git. Then we go to GitHub Actions and trigger the deployment. Give it a few moments to finish. And here we go. Let's go visit the site. And we don't see the results of the ob_cache_get_status function call we just added in the route. And that's because our php script was cached by ob_cache. And what we are seeing now is the old copy we had from before the deployment. And to refresh the cache, let's go to the terminal and ssh into the server and restart php-fpm. So sudo service php8.1-fpm restart. Now let's go back to the browser, refresh. And here we go. Our code changes were reflected. And in here we can see that opcache is enabled and the cache is not full yet. No restarts are pending or in progress. So let's go inspect the memory usage.
Reloading PHP-FPM Safely7:31
the common Laravel and dependencies code will be cached multiple times. That's why it's very important that we watch the OB cache statistics on a regular basis to ensure that we are not hitting any OB cache limits like the maximum memory and the maximum number of cached files. Now in order to refresh the OB cache, we had to restart PHP FPM. That was a bit extreme. Restarting PHP FPM kills all connected clients and shuts down all FPM processes. That means all applications on our server will experience a downtime. And some of the ongoing requests will fail and the application users will get an error. To gracefully refresh the cache, we can reload PHP FPM instead. And reloading PHP FPM requires sudo privileges just like the just like restarting PHP FPM. And if you remember our deployment scripts run as the app system user, which is laracasts in our case. So we need to allow the laracasts
Allowing Reload via Sudoers8:24
sudo privileges just like the just like restarting php-fpm. And if you remember our deployment scripts run as the app system user, which is laracasts in our case. So we need to allow the laracasts user to run this command, the php-fpm reload command, and only this command as sudo. So let's do that. On our VPS while logged as the ubuntu system user, we will use the vsudo command to edit a special system file called sudoers. Editing this file using this command ensures that the file syntax is correct before saving the file. If we edit the file manually, and we have an incorrect syntax like a mistake, while saving the file without validation, this can break the system privileges, which will leave our VPS in a broken state. So let's run sudo vsudo. Now that the file is open, let's scroll down and write the following laracasts ALL=(ALL) NOPASSWD: /path/to/php-fpm reload which indicates all hosts equals no password.
So let's run sudo vsudo. Now that the file is open, let's scroll down and write the following laracasts all which indicates all hosts equals no password. So the system doesn't ask us for a password when we run the command sudo service php8.1-fpm reload. Now let's save the file and then switch to the laracasts user to verify that things are working as we expect. Let's run sudo service php8.1-fpm reload. And here we go. No errors. Let's try to run the service restart command instead of reload. And we get asked for a password. Let's go for it and provide the password. And here we go, we get an error. Sorry, user laracasts is not allowed to execute the restart command. That's because we only allowed the reload command. Now let's copy the reload command and go to our GitHub workflow configuration.
Updating Deployment Workflow10:30
the restart command. That's because we only allowed the reload command. Now let's copy the reload command and go to our GitHub workflow configuration and update the deployment script to include that command. Let's deploy by pushing to git and then go to GitHub Actions to run our workflow. Let's go check what's going on. And here we go. The command ran successfully. No errors. Perfect. Now we have opcache enabled and configured to cache our compiled PHP scripts, which enhances the performance of our application and reduces the CPU power needed to compile the files on every request. We also updated our deployment script to reload PHP FPM, so that the opcache is cleared on every deployment. We also added the reload command to the sudoers file to allow our laracasts system user to reload PHP FPM using the sudo command.
so that the opcache is cleared on every deployment. We also added the reload command to the sudoers file to allow our Laracasts system user to reload PHP FPM using the sudo command. You now have a Ubuntu virtual server that is ready to run your Laravel applications in production. There are many other PHP FPM and Nginx configurations that you can tweak to get the best performance out of your VPS. We will look into these in another Laracasts course. That's the end of this course and I hope you learned something and I will see you in the next one.
