¡Güarnin! ¡Troncho técnico para sysadmins!
La alegría que supone para muchos webmasters aparecer enlazado desde la portada de un sitio con mucho tráfico puede durar muy poco si la máquina no aguanta la presión mediática, se acojona, y cierra la puerta.
Esto pasa normalmente en servidores Apache con la configuración por defecto, y se debe a esto: Apache suele venir configurado para usar PHP mediante mod-php, un modulo que integra PHP en el núcleo de Apache usando el gestor prefork. Lo que esto hace es añadir a cada proceso de Apache todo lo necesario para mostrar páginas PHP, y el problema es que eso hace crecer dichos procesos un buen puñado de megas, pongamos 32 como ejemplo conservador.

Cada proceso de prefork es capaz de gestionar una conexión entrante, por lo que tendremos tantos procesos como conexiones activas, más un grupo de procesos parados, a la espera de nuevas conexiones. Si la página tiene pocas visitas, pongamos una media de 5 visitantes pululando a la vez, esto puede hacer que tengamos unos 10 procesos de Apache, consumiendo unos 300MB.
El problema viene cuando de repente llegan 100 o 200 clientes a la vez, lo que hace que necesitemos 200 x 32MB en nuestro ejemplo. Salvo que el servidor tenga 6GB de RAM se va a hacer caquita encima, y enterrarla en la swap. Pero eso es peor solución que la enfermedad, porque la swap es muy lenta, las peticiones se acumularán, los clientes seguirán reclamando lo suyo (a golpe de F5) y la swap se llenará al cabo de un rato. ¡Poner más swap solo agrava el problema!
La única solución a esto es configurar Apache para que no acepte tantas conexiones, y muestre un penoso mensaje de "estoy ocupado, vuelve luego" a los usuarios, en vez de aceptar más trabajo del que puede manejar. Pero eso es en el caso de estar usando modphp+prefork, que es desde mi punto de vista, una estupidez.
¡FastCGI al rescate!
Cada vez que un usuario pide un archivo CSS, una imagen o cualquier otro contenido estático, estará usando un proceso de Apache para ello, como es lógico. Lo que no es tan lógico es usar para eso un enorme proceso de Apache capaz de compilar PHP en vez de un proceso pequeño y eficiente que solo sirva para enviar archivos estáticos. Pero no queremos montar dos servidores, así que hay varias alternativas.
Una es utilizar servidores alternativos como Lighttpd, Nginx o similares, que sólo sirven contenido estático -y muy bien, por cierto-, y mantienen un servidor fastcgi independiente para servir php, perl, python y similares. De esta forma tanto el contenido estático como el dinámico se muestran eficientemente. Lo malo es que cambiarse a estos servidores puede ser muy incómodo, los rewrites pueden fallar, los .htaccess no siempre funcionan, y hay mucha menos documentación. Por suerte Apache puede usar FastCGI también.
Cirujía
El cambio es relativamente sencillo. Primero hay que instalar libapache2-mod-fcgid y php5-cgi -los nombres de los paquetes pueden cambiar de una distro a otra-. Al hacerlo el instalador mandará a prefork a cavar zanjas e instalará el gestor mpm-worker, que es mucho más eficiente -funciona por threads de forma que cada proceso puede gestionar muchos clientes simultáneos-.
El siguiente paso es configurar nuestros virtualhosts para que usen PHP como cgi, tal y como se hace con perl o python, añadiendo el típico alias:
ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/
<Directory /var/lib/cgi-bin/>
AllowOverride None
Options ExecCGI -MultiViews +SymLinksIfOwnerMatch
Order allow,deny
Allow from all
</Directory>
Lo siguiente es habilitar el modulo fcgid -con el comando a2enmod fcgid- y usar esta configuración para el módulo (generalmente en /etc/apache2/mods-enabled/fcgid.conf):
<IfModule mod_fcgid.c>
AddHandler fcgid-script .fcgi
AddHandler fcgid-script .php
FCGIWrapper /usr/lib/cgi-bin/php5 .php
IPCConnectTimeout 20
Options ExecCGI
</IfModule>
Recargamos Apache, y listo. Esta configuración tiene un montón de ventajas; por ejemplo podemos elegir tener virtualhosts con soporte de PHP o no, para mejorar la seguridad en aquellos que sólo sirvan contenido estático. El rendimiento es muy alto, lo primero que notaremos es que tendremos muchos menos procesos de Apache, y que de estos cuelgan un par de procesos php5. Estos procesos están dedicados exclusivamente a compilar scripts de PHP bajo demanda.
Mejorando la receta
Usar FastCGI no es lo único que podemos hacer para mejorar el rendimiento en varios órdenes de magnitud. Lo primero será cambiar la configuración de gestión de clientes de Apache de la que viene por defecto a una razonable -y que es la culpable del efecto /. en cualquier caso-.
La configuración para trisquel.info es esta:
<IfModule mpm_worker_module>
StartServers 2
MaxClients 250
MinSpareThreads 25
MaxSpareThreads 75
ThreadsPerChild 25
MaxRequestsPerChild 0
</IfModule>
En este momento -drupal dice que hay 23 personas navegando- hay dos procesos de Apache consumiendo 10MB en total, y tres procesos de PHP consumiendo 70MB -gracias a que usan una caché de opcode que explicaré después-. Cada proceso de Apache puede gestionar 25 conexiones simultaneas. No será necesario lanzar un proceso nuevo hasta que sólo queden 25 huecos libres en total, lo que da para 50 conexiones simultaneas, de sobra para salir en la portada de cualquier web grande. Y lanzar un nuevo proceso de apache sólo consumirá un par de megas más, dejando hueco para otras 25 conexiones simultaneas.
También es buena idea bajar los tiempos de timeout para que las conexiones expiren antes, yo uso estos valores:
Timeout 30
KeepAliveTimeout 3
Cacheando PHP
Obviamente todos estos cambios no eliminan la necesidad de una buena política de caché. Si usas un gestor de contenido -WordPress, Drupal, Joomla, etc- este tendrá un sistema de caché que más te vale tener activo. Pero la caché sólo funciona con las páginas que no cambian en cada visualización. Cualquier cosa que cambie en cada recarga -como ese bonito bloque de imágenes aleatorias, o esa frase coñera variada- va a hacer que la caché valga de poco. Pero hay una forma de cachear el PHP a un nivel más bajo.
PHP es un lenguaje de scripting, pero para interpretarse primero se compila, luego se ejecuta, y luego se descarta el código compilado. Esto es obviamente un desperdicio, porque el código no suele variar entre ejecuciones, y serviría con compilarlo una sola vez. Las cachés de opcode -que es como se suele llamar al código intermedio que genera el intérprete- hacen esto. Yo he usado varias: eAccelerator, PHPaccelerator o Turck MMCache. Actualmente uso APC, que es fácil de instalar y muy estable. Basta con instalar el paquete php-apc y poner estos valores en /etc/php5/cgi/conf.d/apc.ini:
extension=apc.so
apc.rfc1867 = 1
apc.shm_size = 50
Esto establece una cache de 50MB -esto depende de las páginas a servir-, compartida por todos los procesos de compilación de PHP gestionados por FastCGI. Tiene la desventaja de utilizar algo más de memoria, pero ahorrará bastante procesador, y el uso de memoria es predecible y controlable. También reduce la necesidad de lanzar nuevos procesos de compilación de PHP -FastCGI hace eso automáticamente cuando es necesario en función del número de clientes-
Bueno, espero que sirva de ayuda. También es posible que alguien mande esto a Menéame y el cpd arda hasta los cimientos. Por listillo.
excelente aporte. Muchas gracias...
Enviar un comentario