2009/08/10

Rendimiento en Drupal (II)

Sobre el tema que ocupaba en el anterior post, continuamos.
El site se diseñó sobre Drupal 5.x, el cual aunque tiene importantes mejoras de rendimiento sobre las versiones 4.x, no llega a lo que puede dar de sí 6.x o incluso la 7.x, actualmente en desarrollo.
Es por ello que tuvimos que implementar una serie de mecanismos para reducir el impacto sobre el hardware disponible. Estos fueron los mecanismos implementados.

- Uso intensivo de memcache. Memcache es un sistema de cachés en RAM, como su nombre indica. Se utilizó para evitar queries masivas sobre la base de datos (mysql). Drupal utiliza la base de datos para prácticamente todo. El ir añadiendo módulos y bloques hace que este uso sea tremendamente intensivo. Esto puede llegar a crear un gran problema. Memcache lo que hace es cachear las queries más habituales en una gran tabla hash, lo que reduce a la mínima expresión (updates e inserts) los accesos a BBDD. El impacto de memcache sobre el site fue brutal, especialmente a nivel de BBDD.
- Uso de las cachés internas de Drupal. Drupal 5.x tiene un sistema propio de cachés. Genera un expires en las páginas, de forma que el usuario que navegue no las recargue cada vez que pase por el site. En 5.x este sistema se limita a eso y poco más. En la versión 6.x se cachea por bloque, lo que produce un importante incremento del rendimiento, pero no así en la 5.x. Además, cada vez que un usuario registrado accede, se limpia la caché. Una lástima. Existen algunos módulos interesantes, como "Advanced Cache" (advcache) que mitiga parte del problema, o Block Cache que fue incluído en Drupal 6.x como caché de bloques. En nuestro caso, utilizamos una versión desarrollada a medida y más flexible de block cache para reducir la carga de site, la cual resultó muy beneficiosa.
- Uso de un proxy reverso. En este caso, squid 2.6. Un sistema de caché reversa puede salvar el pescuezo a cualquiera. Al contrario que la caché interna de Drupal que cachea principalmente objetos, el proxy reverso cachea las páginas renderizadas tal cual, así como los gráficos, css, javascript, etc. Si bien estos últimos apenas suponen carga para el servidor web, sí que implican el uso de procesos, escrituras del log, etc, etc. El cacheo de páginas renderizadas libera realmente al site de producción. En nuestro caso, modificamos drupal para generar dos tipos de URL. Una URL genérica para usuarios anónimos, y una URL con un parámetro para usuarios logados, de forma que el sistema de caché reversa cacheara todos los accesos anónimos (segun drupal indicara en su expires) y liberara de los mismos al servidor. Los usuarios logados seguían cargando (y mucho) el site, pero al menos crawlers, usuarios ocasionales (la gran mayoría) ya no lo hacían.
- PHP eaccelerator. Es un acelerador de PHP. Básicamente lo que hace es almacenar el código PHP ya compilado para que el sistema no tenga que volver a interpretarlo cada vez que una página es invocada. El rendimiento del código se incrementa entre x3 y x5. Hay otros sistemas como APC o Xcache que tienen un impacto similar.
- En MySQL, cambio de MyISAM a InnoDB. Soy poco fan de MySQL y siempre me ha parecido que postgres es un sistema de BBDD mucho más robusto, fiable y serio. Pero es lo que hay. Como estábamos trabajando con MySQL 5, teníamos posibilidad de usar InnoDB en lugar de MyISAM como sistema de almacenaniento. Aunque sobre el papel MyISAM es más rápido a la hora de realizar consultas por su mayor simplicidad, InnoDB es mucho más avanzado por cuanto que no bloquea tablas completas en consultas o updates. En nuestro caso, además, prácticamente no usamos las consultas porque memcache las sirve casi todas, y experimentamos bloqueos por queries un tanto "rudas" contra la BBDD que en algunas ocasiones colgaron el sistema. El paso a InnoDB solucionó este problema. Modificamos algunos puntos del código para eliminar bloqueos explícitos que realiza Drupal 5, pensado para MyISAM, en aras de mejorar el rendimiento, aunque como supondréis mucho, poco incrementó dicho rendimiento habida cuenta de que (otra vez) casi todo se lo come memcache.

Adicionalmente a estas medidas, y como red de seguridad, sugerimos una serie de cambios al proveedor (sí, lo sé, esto no debería haber sucedido jamás, pero el mundo de los sysadmins está lleno de manías y no es fácil decirle a uno lo que tiene que hacer. A todos nos sienta mal que nos lo digan).
- Limitar el número de procesos de Apache (para evitar que el servidor se sature). Mejor hacer esperar al usuario unos segundos encolado, a hacerle esperar para siempre porque nuestro servidor se ha ido a freir monas. Aquí lo ideal es realizar un estudio a grosso modo entre la memoria utilizada por proceso, la memoria de la máquina y la potencia de CPU de la máquina. Esto es, si cada proceso viene a tomar 100MB, tenemos una máquina con 2GB de memoria y un procesador doble núcleo, no parece lógico coger más a allá de 15 procesos en Apache para no quedarnos sin memoria... o sin CPU, si la aplicación es muy intensiva.
- Limitar el número de conexiones a BBDD. Tanto a nivel de gestor como a nivel de php. En este caso, si como máximo vamos a tener 15 procesos, no tiene sentido dar más de 15 conexiones a BBDD. Como siempre, mejor "encolar" que ser "enculados" (no tiene gracia, pero bueno).

Con estos cambios más algunos a nivel de código que realizaron los desarrolladores (desde la parte de administración IT podemos hacer muchas cosas para mitigar, pero a fin de cuentas muchos de los problemas, si no casi todos -que no ha sido en este caso-, se solucionan desde la pantalla del programador) conseguimos que lo que fue una abrupta salida en producción se convirtiera dos días después solo en un problema, y al cabo de dos semanas en un problemilla.

Todavía hay muchas mejoras que se pueden hacer, sobre todo a nivel de cachés de Drupal y optimización de algunos componentes internos, pero a nivel de IT poco más se puede hacer. Y si alguien tiene alguna sugerencia que me la diga, que estoy completamente abierto a nuevos planteamientos.

Rendimiento en Drupal (I)

Recientemente hemos tenido una incidencia en un proyecto basado en Drupal debido a un problema grave de rendimiento. El site es un portal público con un gran número de accesos, y obviamente, al salir a producción emergió este problema.
Normalmente este tipo de situaciones no debería darse si se dimensionan unos benchmarks en condiciones, pero lamentablemente no fue este el caso. Un error muy común a la hora de definir benchmarks es no plantearlos adecuadamente.
Por un lado, hemos de diseñar unos casos lógicos de navegación. De poco basta definir una prueba de carga contra una home si luego la mayor parte de los usuarios se centran en dos o tres páginas concretas. Es importante, así pues, contar con una estadística de accesos al portal previo (caso de una actualización) o con una estimación lo más ajustada posible en caso de una nueva salida. Para este último caso se puede contar con un grupo de usuarios de control que, durante una temporada, hagan uso del portal para así generar un patrón de uso lo más realista posible.
Por otro lado, a la hora de diseñar los patrones de navegación es importante realizar varios patrones diferentes, incluir páginas diferentes en cada uno de esos patrones e incrementar lógicamente el número de hits sobre las páginas que van a ser accedidas con mayor frecuencia.
Todo esto y mucho más se puede realizar perfectamente con Jakarta JMeter.
Finalmente, y para el caso que nos ocupa, en el dimensionamiento de las pruebas, y este fue el GRAN ERROR, no se tuvo en cuenta que no es lo mismo un usuario anónimo que un usuario registrado. Estos últimos, por las características inherentes de Drupal, hacen que se haga flush del sistema de cachés por toda página sobre la que navegas.
El resultado es que aunque los test de carga daban un portal que podría soportar lo que fuera, a la hora de la verdad fue todo lo contrario.
En resúmen, es muy importante plantear y diseñar un sistema de pruebas de carga lo más realista posible a la hora de sacar un site a producción, especialmente cuando son sites sobre los que se va a tener una gran concurrencia.

Chrome en Linux

Hace ya algunos meses que las alphas de Chromium compilan en Linux y más que menos funcionan. Son realmente rápidas, pero además de incluir algunos bugs y funcionalidad sin terminar, no soportan plugins como flash, java y otros.
Si ya de por sí en Linux solemos tener problemas con algunos plugins creados en exclusiva para Windows, que encima los más comunes tampoco se soporten en Chromium hacen que el navegador quede relegado a pruebas o a sites donde queremos velocidad sin importarnos los dibujitos.
Pero esto ha cambiado. Desde hace muy poco, ya podemos igualmente hacer funcionar los plugins sobre Chromium, eso sí, añadiendo algo de inestabilidad al sistema.
Para ello basta con ejecutar Chromium desde línea de comandos:

chromium-browser --enable-plugins

Y automáticamente todo plugin de firefox estará disponible en Chromium (con sus ventajas y desventajas).
Para usuarios de Ubuntu y derivados, tenéis las builds disponibles en lauchpad como de costumbre.