Sunday, April 06, 2008

Escalabilidad en el servidor

Al principio fue Apache, con un modo de funcionamiento extremadamente robusto. Cada petición se sirve en un proceso hijo distinto. Si algo va mal, el proceso hijo se colgaría, pero no afectaría en absoluto al resto de peticiones servidas por los otros procesos. Método lento, ya que crear un nuevo proceso es lento, pero seguro. Para mejorar el rendimiento se suele usar un "pool" (grupo) de procesos latentes, para no tener que crear un nuevo proceso con cada petición. Así nos ahorramos la creación de un nuevo proceso por petición.

Si queremos servir páginas web dinámicas y usamos Apache tenemos principalmente dos posibilidades, usar un "pool" de procesos PHP, qué comunicarán con Apache usando FastCGI, o usar ejecutar PHP directamente desde Apache con mod_php (en lugar de PHP/fastCGI podemos usar Python/SCGI o cualquier otro lenguaje). En el segundo caso, la comunicación entre Apache y el intérprete será más rápida ya que no necesita una comunicación entre procesos (Apache - PHP) por cada petición. Para servir una petición, Apache comunicará con uno de sus procesos libres del pool, y si es una petición dinámica y ese proceso aún no ha cargado mod_php, lo cargará y ejecutará el script correspondiente.

El numero máximo de recursos por segundo que podremos servir antes de agotar la memoría será:

nb_procesos_max = mem_total / mem_proc
recursos_seg = nb_procesos_max / seg_recurso

Donde mem_proc es la memoría que ocupa cada proceso Apache y seg_recurso es el tiempo medio que tardamos en servir un recurso.

Por ejemplo, si tenemos 2 GB de memoria, cada proceso consume 20 MB, y de media tardamos 0.1 segundos en servir una petición, podremos servir un máximo de 2000 / (20 * 0.1) = 1000 peticiones por segundo. Con este modelo, si queremos activar KeepAlives (ver el artículo sobre cómo acelerar la descarga de páginas web) durante 10 segundos, tendremos un límite de 2000 / (20 * 10) = 20 peticiones por segundo. ¡Hemos dividido por 100 nuestro máximo!

Para poder activar KeepAlives sin matar nuestro servidor, o simplemente si queremos consumir menos memoria, tenemos que cambiar de modelo. Sabemos que la mayoría de recursos son simples ficheros estáticos (js, css, png, jpg, etc.). No necesitamos un intérprete PHP para servirlos. De hecho, es tan sencillo que podemos hacerlo directamente desde el proceso principal del servidor web, sin necesidad de usar un proceso secundario ya que la posibilidad de que el proceso se cuelgue haciendo algo tan simple es prácticamente nula. Apache (<= 2.1) no puede funcionar en este modo, y tendremos que usar otro servidor Web, como lighttpd, nginx o cherokee.

Para los recursos dinámicos, podemos mantener un pool de procesos PHP y comunicar con ellos usando fastCGI, o simplemente seguir usando Apache y comunicar con Apache por HTTP (ideal para una migración rápida, ya que podemos seguir usando la configuración previa). La comunicacion entre servidor y PHP será algo más lenta, pero como ventaja desacoplamos el tiempo de vida de la conexión TCP con el del proceso PHP. Lo único que hay que hacer es pedirle a Apache que escuche en otro puerto (Listen 8080) y pedirle al servidor de recursos estáticos que actue de reverse proxy hacia localhost:8080 para todas las peticiones dinámicas.

La mayoría de servidores que permiten este modo de funcionamiento, permiten también usar un pool de procesos para servir los recursos estáticos, pero esta vez un pool pequeño y fijo (por ejemplo de 4 procesos). De este modo podemos aprovechar varios procesadores o un procesador multicore, y paralelizar las peticiones en caso de que alguna llamada al sistema bloquee (en teoría todas las llamadas que se usan son asíncronas, en la práctica a veces bloquean).