Apache 2, PHP and FreeBSD: traps to avoid (and other tricks)
I won't give you the magical recipe which solve all your problems, but just some tricks to avoid many situations you may encounter. This post may be updated if I find any new tricks. Theses tricks will be administration but non development oriented.
first publication on 22/02/2006
- 06/04/2007 : add some tricks about MySQL (temporay tables buffer) and PHP (persistent connections)
This post was redacted with at least this configuration in mind:
- Apache 2.2.3
- PHP 5.1.6
- MySQL 5.0.24
- FreeBSD 6.1-STABLE
Of course, it may be usable for other configurations, or give elements to think about.
Under FreeBSD and not under GNU/Linux, performances drop down where there are too many simultaneous connections. With my 1.8 GHz Celeron and 1.2 GB RAM, I can't go over 30 simultaneous connections without blocking the computer.
This issue seems to be fixed with FreeBSD 7.0, but it may be possible to attenuate it with previous versions.
Here are some ideas:
- Build MySQL with Linux threads. You have to use “WITH_LINUXTHREADS=yes” parameter while building it (the way to use it may vary depending your installation procedure: portupgrade, make install within ports' tree or source install).
- Build MySQL with “BUILD_OPTIMIZED=yes” parameter. This parameter passes more optimized parameters to compiler in order to get better binaries. It's hard to know how much performances are improved, but it can't do any harm.
- Build MySQL with “BUILD_STATIC=yes” parameter. This parameter allows MySQL to be compiled with static links to external libraries. Each time this call to these libraries will be made, it won't be needed to search for this library as the link is hard-coded into executable binary. But if one day you update these libraries, you may break these static links, then need to build MySQL again.
- Start MySQL without DNS resolution. Each time a client connects to MySQL daemon, it solve client's IP address to hostname. With many simultaneous connections, MySQL must wait for name server answer before execute the query, answer which may be slow to come depending on network or system usage. If you want to disable this reverse DNS resolution, you have to add parameter “--skip-name-resolve” when starting MySQL. Under FreeBSD, just add into file “/etc/rc.conf” the line “mysql_args="--skip-name-resolve"” then restart MySQL.
- Setup MySQL memory usage. A database manager need as many RAM as possible: bigger is the cache, quicker are queries execution (it's quicker to access to previous results into RAM than take them from hard disk). But allocate to many memory to MySQL will penalize other system processes. You must find yourself the good values. There are into “/usr/local/share/mysql/” folder many “.cnf” with are many usecase MySQL configuration files. You just have to choose the right file, copy it to “/etc/my.cnf”, and adapt it to your needs. Then restart MySQL.
- Allocate more memory to temporary tables buffer (“tmp_table_size” parameter into “/etc/my.cnf” file). This buffer is used to store temporary tables created while executing some queries. If this parameter is set to low, MySQL will use the harddrive, slower than system memory. Default value is set to 32 MB.
MPM and PHP
Since version 2, Apache support many distinct MPM. The most often used are the following ones:
- prefork: each time a client connects, a new process is created from the father's one. This new process will load all apache modules into memory. It's the old school way used with Apache 1.3.
- worker: each time a client connects, a new thread is created from father's process. This way is less slower than the previous one, because it's not necessary to create a new process with all related Apache modules. As modules are shared between all threads from the same father process, memory usage will be less important.
If you read the previous list, you will want to use worker MPM: less slower, better memory usage, why hesitate ? the problem is that PHP only support prefork MPM, and nothing is guaranteed with worker MPM. It's may work, but it may not. In my case, it perfectly worked with PHP 5.0.x, but didn't worked with PHP 5.1.x. Related symptoms were session lost or blank pages.
Then if you don't want to prematurely loose your hairs, only use prefork MPM. So you may want to stay with Apache 1.3, and you're right. But remember, one day, this version will be obsolete and not supported anymore, and sooner than version 2.0.
PHP and MySQL
You have to ways to connect to a MySQL database: persistent connections, and non persistent connections. A persistent connection is a connection which will stay open until it's explicitly closed (with the related function or when the script ends). If may be useful if you don't want to lose time waiting to open connection again when you need it, but if you have too many simultaneous connexions the MyQSL service may be overloaded.
A non persistent connection is a connection which is open and close each time a query is sent to MySQL. Connection open and close functions are only here to create the variable used to manage the connexion. Unlike persistent connection, you have a slight overhead due to connection opening and closing, but it may be unnoticeable (unless your scripts need to often execute queries). In non persistent connection case, the server will be less often overloaded while getting many simultaneous connections.
In order to disable persistent connections with PHP, you have to set the parameter “mysql.allow_persistent” to “Off” into “/usr/local/etc/php.ini” file.
PHP and memory usage
It may be evident to you, but PHP is memory hungry. After a few quick tries, an Apache process use 7 MB without PHP, and 20 MB with PHP. And don't forget: more you have connections, more you have open Apache processes. With 20 MB per processes, memory usage will grow quickly.
In order to avoid important memory usage, only install PHP modules you need. And if you have problems with a module, you will loose less time to find which one is guilty with few modules than with many. When I write about problems with a module, I mean this: I open a page with use no function from this module, but Apache crashes with an error code 11. If you want to find the guilty module, disable all modules one by one, then restart Apache and reload the page until Apache doesn't crash anymore.
PHP and performances.
PHP is an interpreted language. This means each time a PHP script is opened and executed, PHP engine will need to check the syntax, compile it to a semi-native language ( called “bytecode”), then execute it. For only one PHP script, this syntax check then compilation dedicated time is invisible to the user. But when the web server has to treat hundreds of PHP script within a second, this overhead may be too important.
If you want to accelerate things, there are tools called “PHP accelerators” which optimize PHP scripts loading. When a PHP script is open for the first time, the syntax is checked, it's compiled to bytecode in an optimized may, and this bytecode is saved then executed. Then, each time this script is asked for, PHP engine will use the previously generated bytecode. This time gain may be appreciable.
There are many PHP accelerator. The most known are:
- ZendOptimizer. It's a closed source accelerator, made by Zend, the company behind the Zend Engine (the engine used by PHP).
- eAccelerator. It's a free PHP accelerator, and it's performances are closed to ZendAccelerator's ones.
I first tried eAccelerator, but had some issues with: cache was not updated, some session loose, etc. With ZendOptimizer, I still have these issues, but less often. Then I don't use any of them.
If you want to use a PHP accelerator, you can accelerate ZendOptimizer if you don't use files protected by ZendGuard. You just have to add into “/usr/local/etc/php.ini” file, within ZendOptimizer section, the following lines:
Apache and AcceptFilter
AcceptFilter is a feature based on kernel options used to optimize HTTP connections (see “accf_http” kernel module for FreeBSD). Said like that, it looks good. But I'm really not convinced: processes memory usage grow (45 MB memory usage per process), processes are opened but never closed (which does not help with memory usage). The only good thing is, when system was not overloaded, that connections seemed to answer quicker. Then disable AcceptFilter. With FreeBSD, it's easy: just add a line
into “/etc/rc.conf” (“apache22” may change depending the version you use).
Apache and connections limitation.
I just come back to memory use with some trick to avoid server overload:
- Add RAM, many RAM. Remember Apache won't be the only running process on your computer, and if you use a database manager, it will be happy with many memory for its buffers. In my case, the server has 1.2 GB of system memory, and MySQL is set to use 512 MB of RAM. To these 1.2 GB of RAM, I added 512 MB of swap.
- Think about Apache maximum connections limits. It's useless to let to many clients to simultaneously connect if your server can't handle them. Think about to a maximum of 200 MB swap usage to avoid system overload when it will begin to swap. In my case, I use the following parameterd:
- 512 MB of RAM for Apache processes (1.2 GB - 512 MB for MySQL - 176 MB for other processes). I had 200 MB of swap memory. Apache will be able to access to 712 MB of memory.
- 30 MB per Apache process: 712 / 30 = 24 simultaneous connections. Then I set Apache not to use more than 25 running processes.
I hope these tricks will be useful to you. There are not perfect one because they come from empiric experience.