PHP: Escalabilidad y rendimiento

Posted by victor on February 4th, 2013
php-logoEscalabilidad y rendimiento son dos magnitudes que mantienen una estrecha relación y que, en general, determinan la capacidad de respuesta de una aplicación web. En este artículo describo varias opciones y configuraciones para que nuestras aplicaciones web respondan en tiempo y sean capaces de hacer frente a un crecimiento del número de accesos. No se trata de pensar en escalabilidad y rendimiento desde el minuto cero de nuestro proyecto; Es más bien, tener en mente los pasos posibles  y realizar una codificación que nos facilite estos pasos en el futuro.

¿Qué es la escalabilidad y el rendimiento?

El rendimiento determinan el tiempo de entrega de los resultados de una petición, es decir, como de rápido eres capaz de servir los datos. Imagina que el tiempo medio empleado en cada petición hacia tu web necesita 400 milisegundos. Una mejora de rendimiento sería bajar esa media a 300 milisegundos. La escalabilidad es la capacidad de tu aplicación web de crecer acorde con el crecimiento del número de usuarios que acceden a ella. Escalabilidad y rendimiento están relacionados. A medida que aumentas el rendimiento de una aplicación requiere menos recursos para escalar.  

Optimización de PHP

PHP es un lenguaje interpretado que, en cada petición web, compila y convierte a opcodes nuestros script. Dicha transformación emplea un tiempo que puede llegar a ser considerable en situaciones de alto tráfico. Existen unas aplicaciones llamadas opcodes cache que se sitúan entre el servidor web y PHP y que almacenan los opcodes generados la primera vez que se realiza una petición de manera que las siguientes no es necesario realizar la compilación. Existen numerosas opcodes cache como Wincache para IIS o APC que es una de las más usadas. No solo se deberían de cachear los opcodes de PHP. También es muy recomendable cachear el contenido HTML. Existen muchas páginas generadas dinámicamente cuyo contenido permanece invariable durante largos periodos de tiempo. Es una pérdida de recursos generarlas en cada petición por lo que se hace necesario almacenar el HTML resultante en disco y servidor directamente si no ha sufrido cambio alguno. Los framework más populares como Symfony o Zend ya incluyen este tipo de mecanismos. Si no usas ningún framework que soporte caché de contenido, puedes crearte tu propio mecanismo de caché HTML:
// /var/www/app/utils/cache.php
$timeout = 3600; // Una hora de duración
$file = '/var/www/tmp/cache/' . md5($_SERVER['REQUEST_URI']);

if (file_exists($file) && (filemtime($file) + $timeout) > time()) {
    readfile($file); // Devuelve el contenido cacheado
    exit();
} else {
    // Ejecutamos el contenido
    ob_start();
    register_shutdown_function(function () use ($file) {
        $content = ob_get_flush();
        file_put_contents($file, $content);
    });
}

Balanceadores de carga

El balanceador es una pieza importante cuando nuestra aplicación web alcanza un tráfico elevado. Nos va a permitir repartir el tráfico entrante entre varios servidores. PHP permite el escalado horizontal de forma sencilla, simplemente añadiendo más servidores. Balanceadores de carga hay de muchos tipos, software y hardware. Nuestra elección dependerá del presupuesto e incluso del tipo de hosting elegido. Los Balanceadores software son muy comunes pues no son más que una máquina corriendo en Linux, por ejemplo, a la que se le ha añadido software para gestionar la carga entre varias máquinas. Existe aplicaciones específicas, como Perlbal, para realizar las labores de un balanceador de carga pero la mayor parte de las ocasiones serán realizadas por servidores web como Nginx, Apache o Squid. Además de gestionar la carga de tráfico, todas estas aplicaciones suelen incorporar características adicionales como caché o compresión GZIP de los resultados. Dentro de los balanceadores software, podríamos incluir a aquellos disponibles en hosting cloud como Amazon EC2 que permite repartir la carga entre varias instancias. La otra gran categoría son los balanceadores hardware, máquinas con software y hardware diseñados específicamente para realizar esta tarea como Citrix NetScaler o F5 Big-IP. Además de su tarea principal, suelen actuar como barrera de seguridad o cortafuegos hacia nuestra red interna. Independientemente del tipo de balanceador de carga elegido, existe varios algoritmos para repartir la carga, desde los más simples, que reparte de forma aleatoria, hasta los que tienen en cuenta variables de estado de cada máquina para asignarles el tráfico. El uso de balanceadores de carga requiere tener en mente algunos consejos que pueden influir en el diseño del código de nuestra aplicación:
  • Si usamos algún tipo de caché, como APC, debemos escribir nuestro código asumiendo que tendremos más de una caché, habrá tantas como nodos tengamos soportando carga.
  • La información sobre sesiones o cookies se suelen almacenar en archivos por lo que es una mala opción si se emplean balanceadores de carga. Debemos buscar una alternativa, como almacenarlas en base de datos.
  • Al hilo del problema anterior, si almacenamos recursos como avatares o fotos subidas por los usuarios, debemos pensar en centralizar los recursos.
Aunque al principio nos baste solo con una máquina para soportar todo el tráfico, a medida que vamos codificando nuestro proyecto debemos hacerlo teniendo estas ideas en mente de forma que futuros cambios sean sencillos de hacer.

MySql

La base de datos es el siguiente cuello de botella. Aunque en el rendimiento de este factor no solo depende de la configuración de los servidores sino también del diseño de nuestras bases de datos: índices, tablas... etc. En este artículo me centraré en MySql por ser uno de los gestores de bases de datos más empleados en el mundo.

Configuración maestro-esclavo

MySql permite activar la configuración maestro-esclavo en la que todos los cambios (insert, update, delete) realizados sobre el maestro, se propagan automáticamente al esclavo. Esto supone varios beneficios como repartir la carga ya que esclavos pueden ser varios. Ahora, todas las consultas que realice nuestra aplicación deben quedar separadas en aquellas que realizan solo consulta (select) de las que realizan modificación de datos (insert, update o delete). Todas las consultas que no implique alterar datos se deben dirigir hacia el esclavo mientras que las restantes se realizan sobre el maestro:mysql-maestro-esclavo

Este tipo de configuración presenta un inconveniente y es que la propagación de los datos del maestro al esclavo necesita un tiempo que puede ir desde los milisegundos hasta segundos, dependiendo de la carga. Debes evitar realizar operaciones de modificación de datos e inmediatamente realizar una select porque puede que obtengas información errónea. Si es muy crucial obtener el dato real, podríamos realizar la select sobre el maestro pero en general, es una mala práctica.

Múltiples esclavos

Si llegamos a este punto hemos de felicitarnos pues nuestras aplicación ha triunfado. Ahora tenemos tantas peticiones que necesitamos añadir más esclavos para mejorar los tiempos de respuesta. En la arquitectura anterior, solo existía un esclavo al que realizar peticiones pero ahora, ¿a qué esclavo accedemos? Una solución sencilla consiste en asignar un esclavo a cada servidor web:

maestro-multiples-esclavos

Ahora, cada servidor web tiene configurado su esclavo. Existe una configuración adicional en la que en cada servidor web se instala un esclavo MySql. En general, es un configuración que no me gusta debido a que estás uniendo las necesidades del servidor web con las del servidor de base de datos. Una aplicación web puede hacer uso intesivo de datos y necesitar agregar cinco servidores MySql sin necesidad de aumentar los servidores web.

Configuración maestro-esclavo en Symfony2

Doctrine 2.1 soporta la configuración maestro-esclavo y desde Symfony2 se puede usar.

Comments

comments powered by Disqus