Saturday, December 7, 2019

FCGI (FastCGI) PHP Apache OPcache



FCGI Overview

FCGI is one of many PHP handlers that Apache can use to interpret PHP code. The handlers are responsible for reading / interpreting PHP code then compiling it and executing the code. Selecting the best PHP handler for your server is critical to overall performance.
Once mod_fcgid is enabled, all PHP processing is done via the FastCGI protocol. mod_fcgid starts up a certain amount of persistent processes that listen for new PHP requests and process them when needed. These processes remain alive even if there are no PHP requests, so you must make sure you configure FCGID to run an appropriate amount of processes, otherwise the server might waste a lot of resources on idle PHP processes. Generally speaking, you should keep a few (2 or 3) idle FCGID processes running to quickly respond to new requests, that way you limit idle resource usage, but still allow for FCGID to spawn more processes if traffic picks up. To determine the maximum FCGID processes you will want to determine how much Memory the average PHP process uses, then divide the available server RAM by this number.
There is no benefit in setting the max process limit too high, otherwise you will risk server instability, especially if it starts to utilize swap space due to lack of RAM. Generally 20 or 30 max FCGID processes is a good place to start.

FCGI Versus suPHP Benchmarks

As you can see from the results. FCGI is generally much faster than suPHP, especially with OPcache on. Notice how suPHP performs worse with opcode caching enabled, mainly because suPHP processes are not persistent, so there is no point in caching something that will never get re-used. DO NOT USE APC or OPCACHE with SUPHP. Only use opcode caching with FCGI.
Test Results below were gathered using a LiquidWeb's 1GB SSD VPS
FCGI SUPHP OPCACHE BENCHMARKS.png

How to enable FCGI PHP handler on cPanel

The first thing to check is whether or not FCGI is listed as an available PHP handler. Run this command to see what the current handler is, and what handlers are available.
/usr/local/cpanel/bin/rebuild_phpconf --current

Available handlers: fcgi cgi none
If you do not see fcgi listed then you will need to run EasyApache and select Mod FastCGI. I also recommend upgrading PHP to at least 5.5 and making sure that Apache is updated to at least 2.4 with the EVENT MPM enabled.
/scripts/easyapache
EasyApache normally takes about 10 minutes or less to finish rebuilding the configuration, once this is done you should be able to log into WHM search for "PHP" and then click on "Configure PHP and suEXEC" and select FCGI as the handler, then save the config just like the image below.
WHM FCGI PHP HANDLER.png

mod_fcgid cpanel file locations

When you activate FCGI as the PHP handler on a cPanel server, there will be a new ifmodule placed in this includes file
vim /usr/local/apache/conf/includes/pre_virtualhost_global.conf
By default, cPanel applies these settings. Obviously this is not ideal, so use the configuration settings listed in the sections below to properly configure and [optimize] fcgi.
<IfModule mod_fcgid.c>
    FcgidMaxRequestLen 1073741824
</IfModule>

FCGI Considerations for Shared Servers

One thing to keep in mind if you plan on using FCGI on a server that has say, 21 + cPanel accounts on it. If the server has 1GB of RAM, you might set FcgidMaxProcesses to 20 to limit memory usage and prevent too many requests from locking up the server due to too much swap usage, or overloading your CPU.
This is all fine in most cases, but what happens if 20 of the sites already has a PHP process running as their own user, and someone tries to view website #21? Nothing happens, the website will hang and try and load for a long time, waiting for one of the occupied 20 FCGI processes to die off and be reborn again so it can serve user #21. In some cases this take be a minute or more. Obviously this is not a good thing, customer #21 is going to be mad their site doesn't load. 
Why does this happen? Because of the way FCGI handles processes and what to do with them once they are done handling requests. For a single website, the ideal FCGI configuration would be to create 10 processes, or however many you want, then set their life time to a large number, like 600 seconds or more. You would do this because of opcode caching, it's best to reuse cached PHP as much as possible to reduce CPU load.
For a shared server though, long running PHP processes are not ideal unless you have enough memory to give every user a single FCGI process, but if you have limited memory and 1000 cpanel accounts, that's not going to be possible since 1000 processes using 32MB each is probably more than 2GB or whatever small amount of memory your server might have.
If you have a server with limited memory and lots of cpanel accounts and you can't set FcgidMaxProcesses to a value higher than the # of users, then your only option is to significantly reduce the timeouts, lifetimes and increase scan intervals to kill off idle processes faster, and create new processes sooner.
While I can't say this would be the best configuration, I will say that it will reduce the chances of a "spinning website" or one that takes 30 seconds to load. By using 5 second intervals the worst case would be a 5 second delay, which sucks, a lot, but it's better than 30 seconds or more. For this to happen, 21 people all over the world would need visit 20 sites on your server within the same 5 seconds, then one other person would need to visit site #21 to run into the issue I described above, but it can happen.
Anyway, if this sounds like the problem you are having, try to use very low idle timeout and process lifetimes, this may reduce performance a but, but FCGI is still WAAAAY better than SuPHP.
<IfModule mod_fcgid.c>
    FcgidMaxRequestLen 1073741824
    FcgidMinProcessesPerClass 0
    FcgidMaxProcesses 20
    FcgidIdleTimeout 3
    FcgidProcessLifeTime 30
    FcgidIdleScanInterval 0
    FcgidErrorScanInterval 0
    FcgidZombieScanInterval 0
    FcgidSpawnScoreUpLimit 7000
    FcgidSpawnScore 1
    FcgidTerminationScore -1
    FcgidTimeScore 3
</IfModule>
I ran some quick tests to try and show how the IdleScanInterval and IdleTimeout settings can effect website load time. The results below were gathered on a cPanel server. I created 15 accounts and installed WordPress on each account. I then configured cPanel to use FCGI as the PHP handler and enabled PHP 5.5's built in OPcache. I limited FcgidMaxProcesses to 10 to replicate the issue. The theory was that after I curl the first 10 test sites, the other 5 sites would take significantly longer to load because the 10 FCGI processes would remain idle until the timeouts / scans decided it was time to kill the process off.
New processes can only be created once one of the existing 10 processes dies. As you can see here the most dramatic improvement happened when I lowered IdleTimeout from 300 (Orange Column) to 30 (Yellow Column). The longest delay between a page load dropped from 1 minute to 35 seconds.
Since IdleTimeout seems to be a huge factor, I lowered it again, from 30 seconds to 3 seconds (Blue Column). As you can see the delay dropped from 35 seconds to 9 seconds. Not bad, but if my website took 9 seconds to load, or even begin to display content I would not be happy. The green column looks much better, with a delay of 3 seconds. If you want to have the green column performance, you can have it! Below this section are some configurations based on various amounts of RAM which will provide good performance and will quickly kill off idle FCGI processes so that new ones can be created.
FCGI max processes website loading delay hangup.png
This is not an issue that will happen very often. The script I used is requesting 15 webpages as fast as it can. For the same situation to happen in the real world, 15 people would need to request 15 sites on your server within the same 6 seconds to notice a slight delay. On servers with a lot of traffic, this might be a common thing to have happen, but I'm testing out these settings for a 1GB VPS server, which is rather small. If I had 2GB of memory I would have raised FcgidMaxProcesses to at least 20, and none of this would be an issue.

FCGI Settings for Servers with only a few cPanel Accounts

  • Use Apache 2.4 Event
  • Use FCGI or PHP-FPM to handle PHP
  • Use PHP 5.5 or newer
  • Enable Zend OPcache for PHP

1GB VPS / cPanel Server

If the server already has PHP 5.5+, enable Zend OPcache. If it does not have PHP 5.5 then you should consider upgrading PHP. Otherwise you can still use fcgi, but you won't be able to use the built in opcache.so. You can still use Xcache or APC or whatever you prefer with PHP 5.4 so don't worry too much about upgrading to 5.5, but still, in general you want to run the latest version of PHP.
To enable opcache for PHP, modify the server's php.ini file
vim /usr/local/lib/php.ini
If cPanel is using PHP version 5.5 or later, the opcache.so extension should already exist. All you need to so is paste this at the bottom of php.ini, once you save the file, restart apache and you should see Zend OPcache listed when you run php -m
zend_extension=/usr/local/lib/php/extensions/no-debug-non-zts-20121212/opcache.so
opcache.memory_consumption=64
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=7963
opcache.revalidate_freq=0
opcache.fast_shutdown=1
opcache.enable_cli=1
opcache.enable=1

The next step is to make sure we are using Apache with the Event MPM. Don't worry too much about messing around with Apache settings, the ones I have below are the default setting. Don't add this to the main httpd.conf file, instead add this to pre_virtualhost_global.conf, or use the WHM includes editor.
vim /usr/local/apache/conf/includes/pre_virtualhost_global.conf
##Apache Event Default Settings (overrides main httpd.conf)
KeepAlive               On
KeepAliveTimeout        2
MaxKeepAliveRequests    500
<IfModule event.c>
ThreadsPerChild         25
ServerLimit             16
MaxRequestWorkers       400
StartServers            6
MinSpareThreads         150
MaxSpareThreads         400
MaxRequestsPerChild     0
</IfModule>

<IfModule mod_fcgid.c>
FcgidMaxRequestLen                      1073741824
FcgidOutputBufferSize                   1073741824
FcgidMinProcessesPerClass               0
FcgidMaxRequestsPerProcess              0
FcgidInitialEnv PHP_FCGI_MAX_REQUESTS   0
FcgidInitialEnv PHP_FCGI_CHILDREN       0
FcgidMaxProcesses                       20
FcgidMaxProcessesPerClass               10
FcgidFixPathinfo                        1
FcgidIdleTimeout                        3
FcgidBusyTimeout                        300
FcgidProcessLifeTime                    300
FcgidIOTimeout                          300
FcgidIdleScanInterval                   1
FcgidErrorScanInterval                  1
FcgidZombieScanInterval                 1
</IfModule>
Go ahead and stop, then start apache, don't just restart it. I find that sometimes if you change start server settings, or other directives a restart does not always apply the changes, not sure if this is a cpanel thing or what.
service httpd stop; service httpd start

2GB VPS / cPanel Server

vim /usr/local/lib/php.ini
zend_extension=/usr/local/lib/php/extensions/no-debug-non-zts-20121212/opcache.so
opcache.memory_consumption=64
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=7963
opcache.revalidate_freq=0
opcache.fast_shutdown=1
opcache.enable_cli=1
opcache.enable=1
vim /usr/local/apache/conf/includes/pre_virtualhost_global.conf
##Apache Event Default Settings (overrides main httpd.conf)
KeepAlive               On
KeepAliveTimeout        2
MaxKeepAliveRequests    500
<IfModule event.c>
ThreadsPerChild         25
ServerLimit             16
MaxRequestWorkers       400
StartServers            6
MinSpareThreads         150
MaxSpareThreads         400
MaxRequestsPerChild     0
</IfModule>

<IfModule mod_fcgid.c>
FcgidMaxRequestLen                      1073741824
FcgidOutputBufferSize                   1073741824
FcgidMinProcessesPerClass               0
FcgidMaxRequestsPerProcess              0
FcgidInitialEnv PHP_FCGI_MAX_REQUESTS   0
FcgidInitialEnv PHP_FCGI_CHILDREN       0
FcgidMaxProcesses                       40
FcgidMaxProcessesPerClass               20
FcgidFixPathinfo                        1
FcgidIdleTimeout                        3
FcgidBusyTimeout                        300
FcgidProcessLifeTime                    300
FcgidIOTimeout                          300
FcgidIdleScanInterval                   1
FcgidErrorScanInterval                  1
FcgidZombieScanInterval                 1
</IfModule>
service httpd stop; service httpd start

4GB VPS / cPanel Server

vim /usr/local/lib/php.ini
zend_extension=/usr/local/lib/php/extensions/no-debug-non-zts-20121212/opcache.so
opcache.memory_consumption=64
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=7963
opcache.revalidate_freq=0
opcache.fast_shutdown=1
opcache.enable_cli=1
opcache.enable=1
vim /usr/local/apache/conf/includes/pre_virtualhost_global.conf
##Apache Event Default Settings (overrides main httpd.conf)
KeepAlive               On
KeepAliveTimeout        2
MaxKeepAliveRequests    500
<IfModule event.c>
ThreadsPerChild         25
ServerLimit             16
MaxRequestWorkers       400
StartServers            6
MinSpareThreads         150
MaxSpareThreads         400
MaxRequestsPerChild     0
</IfModule>

<IfModule mod_fcgid.c>
FcgidMaxRequestLen                      1073741824
FcgidOutputBufferSize                   1073741824
FcgidMinProcessesPerClass               0
FcgidMaxRequestsPerProcess              0
FcgidInitialEnv PHP_FCGI_MAX_REQUESTS   0
FcgidInitialEnv PHP_FCGI_CHILDREN       0
FcgidMaxProcesses                       80
FcgidMaxProcessesPerClass               40
FcgidFixPathinfo                        1
FcgidIdleTimeout                        3
FcgidBusyTimeout                        300
FcgidProcessLifeTime                    300
FcgidIOTimeout                          300
FcgidIdleScanInterval                   1
FcgidErrorScanInterval                  1
FcgidZombieScanInterval                 1
</IfModule>
service httpd stop; service httpd start

8GB VPS / cPanel Server

vim /usr/local/lib/php.ini
zend_extension=/usr/local/lib/php/extensions/no-debug-non-zts-20121212/opcache.so
opcache.memory_consumption=64
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=7963
opcache.revalidate_freq=0
opcache.fast_shutdown=1
opcache.enable_cli=1
opcache.enable=1
vim /usr/local/apache/conf/includes/pre_virtualhost_global.conf
##Apache Event Default Settings (overrides main httpd.conf)
KeepAlive               On
KeepAliveTimeout        2
MaxKeepAliveRequests    500
<IfModule event.c>
ThreadsPerChild         25
ServerLimit             16
MaxRequestWorkers       400
StartServers            6
MinSpareThreads         150
MaxSpareThreads         400
MaxRequestsPerChild     0
</IfModule>

<IfModule mod_fcgid.c>
FcgidMaxRequestLen                      1073741824
FcgidOutputBufferSize                   1073741824
FcgidMinProcessesPerClass               0
FcgidMaxRequestsPerProcess              0
FcgidInitialEnv PHP_FCGI_MAX_REQUESTS   0
FcgidInitialEnv PHP_FCGI_CHILDREN       0
FcgidMaxProcesses                       160
FcgidMaxProcessesPerClass               80
FcgidFixPathinfo                        1
FcgidIdleTimeout                        3
FcgidBusyTimeout                        300
FcgidProcessLifeTime                    300
FcgidIOTimeout                          300
FcgidIdleScanInterval                   1
FcgidErrorScanInterval                  1
FcgidZombieScanInterval                 1
</IfModule>
service httpd stop; service httpd start

mod_fcgid configuration settings

FcgidMaxRequestLen

If the size of the request body exceeds this amount, the request will fail with 500 Server Error. By default this is set to 131072 Bytes. For cPanel the default is 1073741824, which is about 1GB.
<IfModule mod_fcgid.c>
    FcgidMaxRequestLen 1073741824
</IfModule>

FcgidMaxProcessesPerClass

This directive sets the maximum number of processes that can be started for each process class, turns out that class means PER USER. If you have multiple users, or have a cPanel server please DO NOT USE THIS. Use FcgidMaxProcesses which sets the global limit. If you set the per class value, it gets multiplied by the amount of users. By default this value is set to 100. If you do not have lots of RAM, lower this to around 20 or so, assuming your app uses about 30MB per process.
<IfModule mod_fcgid.c>
    FcgidMaxProcessesPerClass 100 (default)
</IfModule>

FcgidMaxProcesses

This sets the global / server limit for how many PHP processes can run. Unlike the FcgidMaxProcessesPerClass directive, which is a per user limit, this directive is for all users. This is basically MaxClients but for PHP. If you have more users than processes, you may run into an issue where some websites appear to hang up for well over a minute. Read on to find out how to get around this issue by lowering various timeout and scan interval settings.
<IfModule mod_fcgid.c>
    FcgidMaxProcesses 20
</IfModule>

FcgidMaxRequestsPerProcess

FastCGI application processes will be terminated after handling the specified number of requests. By default this is set to 0 which means there is no limit / check.
<IfModule mod_fcgid.c>
    FcgidMaxRequestsPerProcess 0
</IfModule>

FcgidConnectTimeout

This is the maximum period of time the module will wait while trying to connect to a FastCGI application on Windows. (This directive is not respected on Unix, where AF_UNIX defaults will apply.)

FcgidIOTimeout

This is the maximum period of time the module will wait while trying to read from or write to a FastCGI application. If you have a slow application you may want to raise this, however 40 seconds is plenty of time. IF you site / app takes 40 seconds to load then odds are no one will ever want to visit your site again.

FcgidIdleTimeout

Application processes which have not handled a request for this period of time will be terminated, if the number of processses for the class exceeds FcgidMinProcessesPerClass. A value of 0 disables the check. The default value is 300 seconds, which means that a FCGI process could serve 1 request, and then remain idle for up to 1 minute or more. This can be a huge waste of resources if you have lots of sites on your server.
<IfModule mod_fcgid.c>
    FcgidIdleTimeout 300 (default)
</IfModule>
If you notice that some websites are hanging up when you try to load them, it could be because all the FCGI processes are active and assigned to a user. If you have more users than FCGI processes then lowing FcgidIdleTimeout to a smaller value like 30, or even 3 will significantly reduce the chances of this happening.
<IfModule mod_fcgid.c>
    FcgidIdleTimeout 3
</IfModule>

FcgidMinProcessesPerClass

This directive sets the minimum number of processes that will be retained in a process class after finishing requests. By default the value is 3.
<IfModule mod_fcgid.c>
    FcgidMinProcessesPerClass 3
</IfModule>

FcgidProcessLifeTime

Idle application processes which have existed for greater than this time will be terminated, if the number of processses for the class exceeds FcgidMinProcessesPerClass. A value of 0 disables the check. By Default this is set to 3600, if you want to save RAM you can set this to something like 30 to kill off unused processes
<IfModule mod_fcgid.c>
    FcgidProcessLifeTime 60
</IfModule>

FcgidIdleScanInterval

This is the interval at which the module will search for processes which have exceeded FcgidIdleTimeout or FcgidProcessLifeTime. The default is 120 seconds, you can lower this if you want shorter living processes. If you notice that on occasion, some websites seem to hang for over a minute or more when you try to load the page, you will want to lower this setting. If all FCGI processes are in use, and you have more users than the process limit, lowering this to 5 or even 0 will cause processes to clear out much faster instead of hanging around. Setting this to 0 is a valid way to go. You will need to lower some other scan intervals besides this one to make sure that processes are not hanging around wasting space.
<IfModule mod_fcgid.c>
    FcgidIdleScanInterval 120 (default)
</IfModule>

FcgidTerminationScore

Lowing the FcgidTerminationScore value increases the allowed spawn rate, the value can be negative which can be useful for allowing process replacement without increasing the score. I did notice that lowering FcgidTerminationScore to 1 did seem to slightly boost performance while running Apache Benchmark, a basic Wordpress blog was requested 100 times and with a score of 2 the server pumped out 23 request/s. With a score of 1 the server was able to reach 29 request/s. Values that were lower than 0 seemed to reduce performance slightly, but every server / website will perform differently so you might want to test the differences for yourself.
<IfModule mod_fcgid.c>
FcgidTerminationScore 2 (default)
</IfModule>

FcgidTimeScore

The value set for FcgidTimeScore determines the amount subtracted from the activity score of a FCGI process each second. Higher values increase the spawn rate, usually this means faster response times, and potentially higher amounts of CPU usage under large amounts of traffic. I've found that raising FcgidTimeScore to 3 seems to help with clearing old processes out and quickly creating new processes to handle new user requests.
<IfModule mod_fcgid.c>
FcgidTimeScore 1 (default)
</IfModule>

Zend opcache

Zend OPcache Links

opcache.max_accelerated_files

opcache.max_accelerated_files - Controls how many PHP files can be held in memory at the same time. Should be set to a Prime number for better performance. { 223, 463, 983, 1979, 3907, 7963, 16229, 32531, 65407, 130987 }
To find out how many files you have, you can use a command like this.
find . -type f -print | grep php | wc -l
I set this to 7963 for my 1GB server and this value seems to work well.
opcache.max_accelerated_files = 7963

opcache.memory_consumption

opcache.memory_consumption - The default is 64MB, if you have a ton of sites or large amounts of PHP code in general you might want to raise this. For a 1GB server I set this to 64MB.
opcache.memory_consumption = 64

opcache.interned_strings_buffer

opcache.interned_strings_buffer - PHP uses string interning to improve performance. If you have the string "mahstring" 100 times in your code, PHP will store 1 variable for the string and will use a pointer to it for the other 99 times it gets used. This value is in MB. Raising this value too high causes each PHP process to use more RAM, or at least appear to. When this was set to 16MB each PHP process was around 48MB. When set to 4MB each PHP process used around 32MB RAM each. This is shared RAM so the usage may not matter much, but just keep that in mind when you tune this and use high values.
opcache.interned_strings_buffer = 4

opcache.revalidate_freq

opcache.revalidate_freq How often in seconds should the code cache expire and check if your code has changed. 0 means it checks your PHP code every single request which can hurt performance. If you don't update PHP code all that often then setting this to 120 seconds could help with performance and still get refreshed every 2 minutes.
opcache.revalidate_freq=120

Using a Custom / Local php.ini with FCGI

cd /home/$user/public-html/cgi-bin
cp /usr/local/lib/php.ini /home/$user/public_html/cgi-bin/
vim /home/$user/public_html/cgi-bin/php.fcgi

##Create the file with this##

#!/bin/sh
export PHP_FCGI_CHILDREN=0
export PHP_FCGI_MAX_REQUESTS=0
exec /usr/local/cpanel/cgi-sys/php5
Save the file, then make sure it is executable
chmod +x /home/$user/public_html/cgi-bin/php.fcgi
chown -R $user:$user /home/$user/public_html/cgi-bin/
Add this to the user's .htaccess file
vim /home/$user/public_html/.htaccess

##Add to top##

AddHandler php5-fastcgi .php
Action php5-fastcgi /cgi-bin/php.fcgi
Now it's time to modify the php.conf file, but before you do, please back up the original one.
cp -a /usr/local/apache/conf/php.conf /usr/local/apache/conf/php.conf.`date +%F.%H.%M`
vim /usr/local/apache/conf/php.conf 
##Make sure these two lines are in the conf##

Action php5-fastcgi /cgi-bin/php.fcgi
AddType application/x-httpd-php .php
Save the file and distill the apache conf and restart apache
/usr/local/cpanel/bin/apache_conf_distiller --update
/scripts/rebuildhttpdconf
/etc/init.d/httpd restart
Create a php info file to test out the changes
vim /home/$user/public_html/phpinfo.php

##Add this##
<?php
phpinfo();
?>
Make sure the php info file has the correct ownerships
chown $user:$user /home/$user/public_html/phpinfo.php
Near the top of the page you should see that the custom path / php.ini file is in use
Loaded Configuration File /home/user/public_html/cgi-bin/php.ini

Additional Information

Video: Scaling PHP in the Real World - Dustin Whittle


Links


Reference:
Previous Post
Next Post

0 komentar: