Optimización AJAX 2: compresión GZIP

Habilitar la compresión de la respuesta puede reducir la cantidad de datos enviados alrededor de un 70%. La mayoría de los navegadores soporta recibir contenidos, e informan de esta capacidad al servidor al realizar una petición.

Es posible agregar la indicación de comprimir la respuesta del servidor en su configuración de acuerdo al mime-type de la respuesta: text/html, text/plain, application/json, application/xml (y en general, es conveniente activarlo para cualquier lenguaje basado en texto, como text/html, text/css, application/javascript, application/json).

Habilitar compresión en Apache y nginx

En Apache puedes habilitar la compresión gzip añadiendo las siguientes líneas a la configuración del servidor o el archivo .htaccess relevante:

<ifModule mod_gzip.c>
mod_gzip_on Yes
mod_gzip_dechunk Yes
mod_gzip_item_include file .(html?|txt|css|js|php|pl)$
mod_gzip_item_include handler ^cgi-script$
mod_gzip_item_include mime ^text/.*
mod_gzip_item_include mime ^application/x-javascript.*
mod_gzip_item_exclude mime ^image/.*
mod_gzip_item_exclude rspheader ^Content-Encoding:.*gzip.*
</ifModule>

En el caso de nginx puedes activarlo dentro del bloque http de la configuración de modo que quede disponible para todos los virtual hosts configurados o bien por bloques server o incluso location.

En Ubuntu estos parámetros suelen venir en la configuración por defecto pero comentados.

gzip on;
# deshabilitar para IE 4 a IE6
gzip_disable "msie6";

gzip_vary on;
# des/habilitar compresión para respuestas de las nginx hace de proxy
gzip_proxied any;
# nivel de compresión, de 1 a 9
gzip_comp_level 4;
gzip_buffers 16 8k;
gzip_http_version 1.1;
# los tipos de respuesta que se enviarán comprimidos. siempre incluye text/html
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;

Habilitar compresión desde PHP

También es posible activar la compresión de la respuesta en la misma aplicación; por ejemplo en PHP esto se puede lograr de forma muy sencilla con ob_gzhandler:

/**
 * Si el navegador no soporta Gzip,
 * la condición retorna falso y se abre un buffer normal
 */
if ( ! ob_start('ob_gzhandler') ) ob_start();
echo $out;
ob_end_flush();
exit;

Cómo comprobar si la respuesta fue comprimida

Puedes comprobar si la respuesta se está enviando efectivamente comprimida con las herramientas de desarrollo de Chrome (debes habilitar la opción use large request rows, junto a la opción para mostra los filtros de contenido).

2b90b5d301En la columna Size, puedes ver dos cifras: la primera corresponde al tamaño del cuerpo de la respuesta, mientras que la segunda a los datos transferidos. Si la cantidad de datos transferidos es menor al tamaño de la respuesta, significa que la compresión está funcionando.

Optimización AJAX 1: elección del método HTTP

Si bien las interacciones en AJAX nos permiten obtener información de forma dinámica desde el servidor de un sitio web o aplicación, existen varias formas de aumentar la velocidad de estas respuestas para mejorar la experiencia de nuestros usuarios al cargar información bajo demanda, obtener datos de búsquedas u otras interacciones.

Más allá de la parafernalia tecnológica que supone la implementación de una respuesta AJAX (por supuesto, reducida al mínimo con el uso de un framework adecuado), ésta sigue siendo una respuesta HTTP, por lo que se pueden aplicar los mismos principios para mejorar su performance.

En éste y los siguientes posts intentaré revisar la teoría y práctica de las técnicas fundamentales para mejorar la performance de respuestas AJAX y la experiencia de tus usuarios.

Continue reading “Optimización AJAX 1: elección del método HTTP”

Herramientas para la optimización de MySQL

En un trabajo de optimización es necesario revisar cientos de factores que pueden estar influyendo en el funcionamiento de un sistema, entre los que la base de datos puede tener un rol clave. Afortunadamente existen múltiples herramientas automatizadas que nos pueden facilitar este trabajo, o al menos darnos un punto de partida para poder ajustar las configuraciones de MySQL de modo de sacar el máximo provecho al hardware que estás utilizando.

  • mysqltuner es una de las alternativas más conocidas y por lo mismo se puede encontrar en los repositorios de varias distribuciones, pero en caso que no sea así la puedes descargar desde su repositorio en GitHub. Dado que se trata de un “simple” script en Perl, basta bajarlo y otorgar permisos de ejecución para obtener una serie de datos críticos y sugerencias de optimización, orientadas tanto a mejorar el performance como la estabilidad.
  • mysql-tuning-primer funciona de modo similar, y lo puedes descargar desde su página de proyecto en Launchpad. En este caso es un script en bash, que a partir de las variables de status de MySQL intenta generar recomendaciones sensatas para optimizar el servidor. Además de las sugerencias de configuración, en varios casos agrega información adicional para comprender de mejor forma el contexto de los ajustes en caso que no tengas un conocimiento muy acabado de las variables en las que indica modificaciones.

Aunque ambas herramientas entregan información muy valiosa para realizar mejoras en el funcionamiento de tu base de datos, es fundamental complementarlas con un conocimiento acabado de cada una de las variables de configuración y el funcionamiento de la aplicación para abordar exitosamente la tarea de optimización.

Pruebas de performance, carga y de estrés

Una ayuda-memoria breve, para tener claros tres conceptos que a veces se confuden:

  • Una prueba de performance tiene como objetivo eliminar “cuellos de botella” en la ejecución de una aplicación y poder determinar una medida aceptable para su funcionamiento, por ejemplo, cuál es un tiempo de respuesta aceptable y la carga esperada en relación al número de usuarios concurrentes. Con estas metas definidas, comienza un proceso de poder detectar y optimizar los procesos donde se entrampa la ejecución del software.
  • En un test de carga, el proceso consiste en aumentar progresivamente la carga en el sistema, a través de herramientas automatizadas; en una aplicación web esto significa examinar cómo se comporta la aplicación con distintos niveles de usuarios concurrentes. El objetivo principal es asegurar que la aplicación alcanza los objetivos de performance establecidos en las pruebas de performance.
  • Las pruebas de estrés buscan forzar una falla del sistema a través del consumo excesivo de sus recursos; con el objetivo de examinar cómo falla y vuelve a su funcionamiento normal.

A partir de esto, podemos entender que estos tres tipos de pruebas apuntan a dominios y objetivos distintos: el primero, a la optimización (que la aplicación funcione rápido), el segundo a la resistencia (que la aplicación funcione rápido aun con muchos usuarios), y el tercero, a la recuperabilidad (que la aplicación continúe funcionando incluso después de que el servidor se haya reiniciado o se haya agotado su espacio en disco).

En el proceso de optimización, además de las mejoras propias al funcionamiento de la aplicación, debemos considerar una serie de mecanismos alternativos para poder asegurar su buen funcionamiento, tales como:

  • Optimización del servidor de base de datos (por ejemplo, caché de consultas, contemplar un/os servidor/es dedicado/s para bases de datos, etc).
  • Caché de objetos para evitar lecturas desde base de datos (memcached, APC, etc).
  • Utilización de sistemas de caché estático (nginx como proxy inverso, Varnish, etc).
  • Escalar vertical u horizontalmente los servidores web (a través de un balanceador de carga).

… aunque como leí por ahí, hay que tener en cuenta que optimizar para performance (solamente para performance) no es lo mismo que optimizar para carga (es decir, pueden existir algunas alternativas que permitan servir una mayor cantidad de usuarios simultáneos, pero no necesariamente de forma más rápida para cada uno de ellos).

Más información en: Agile Testing: Performance vs. load vs. stress testing

Foto: Shell Games por Lazlo Ilyes

Probando…

Experimentando con algunas alternativas para un mejor rendimiento.

Si has tratado de ingresar a este blog durante los últimos días, es probable que te haya tocado la desagradable experiencia de encontrate con alguno que otro error… la culpa es mía, lo siento 😛

La causa es simple: hasta hace un par de días, aún estaba utilizando la versión 4 de [PHP->@wiki], la que aparte de ser bastante antigua, hace ya un rato que no está soportada oficialmente (y, de hecho, sólo tendrá parches de seguridad hasta el próximo mes). Por lo tanto correspondía el cambio a PHP 5, y puesto que ya estaba metido modificando cosas, me entraron las ganas de probar algunas alternativas que podrían mejorar el rendimiento del blog, en especial FastCGI+eAccelerator.

De resultar perfectamente bien, la mejora en el funcionamiento debería ser notable, pero hasta ahora todavía estoy en una etapa de pruebas, con la consiguiente inestabilidad en el funcionamiento del blog. contrasentido también está haciendo las veces de conejillo de indias, esperando que entre estos dos sitios pueda tener la información suficiente para ver cuál es la opción más conveniente en términos tanto de consumo de recursos como de estabilidad: FastCGI+eAccelerator, sólo FastCGI o simplemente PHP 5 como CGI (la opción predeterminada de DreamHost)

Así que, por el momento, habrán fallas, pero pronto habrán resultados.

PHPSpeedy: mejora el tiempo de carga de tu sitio

PHP Speedy Hay muchas formas de mejorar el tiempo de carga de una página; algunas de ellas son:

  • realizar menos peticiones al servidor
  • agregar una cabecera de expiración muy lejana en el futuro
  • comprimir los componentes de la página
  • “minificar” tu javascript, css, html

PHP Speedy es un script para PHP que realiza estas cuatro tareas automáticamente. Está disponible como clase (para utilizar en cualquier sitio/página que soporte PHP) o como plugin para WordPress —he estado probando este último en mi tumblelog y el resultado ha sido bastante satisfactorio.

Si quieres utilizar el plugin para WordPress, debes asegurarte que tu theme incluya el hook <?php wp_head(); ?> antes de </head>. Además, revisa que todo lo que tenga que ver con JavaScript siga funcionando: el plugin combinará y “minificará” tus scripts y los enlazará en el punto donde se encuentre <?php wp_head(); ?>. Si tienes código que necesita de librerías que se hayan invocado anteriormente, puedes solucionarlo simplemente moviendo tu código más abajo de <?php wp_head(); ?>.

Incrustar imágenes en hojas de estilo

Un artículo dedicado a las URL de datos en hojas de estilo: cómo incrustar una imagen, cuándo y sus desventajas.

A pesar de que no es una técnica muy ampliamente utilizada, la capacidad de poder insertar imágenes directamente en una hoja de estilo es una alternativa que en ocasiones bien podría estar no solamente justificada, sino derechamente preferida por sobre la típica referencia mediante una URL

Aclaro de qué estoy hablando: cuando vamos a aplicar una imagen de fondo a algún elemento mediante CSS, generalmente lo hacemos de esta forma

.lorem-ipsum{
	background-image: url(http://servidor.com/ruta/a/imagen.jpg);
}

Al “incrustar” una imagen, lo haríamos de este modo:

.lorem-ipsum{ 
	background-image: 
url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOYAAAAVCAMAAABlsmI+A 
AAAA3NCSVQICAjb4U/gAAAAS1BMVEX////////29vbv7+/m5ubf39/W1tbMzMzExMS9vb61t 
bWtra2lpaWZmZmVlZWLi4uEhIR7e3tycnJra2tgYGBaWlpTU1NJSUlEREQYZf5IAAAAGXRST 
lMA////////////////////////////////MFNJ3QAAAAlwSFlzAAALEgAACxIB0t1+/AAAA 
BV0RVh0Q3JlYXRpb24gVGltZQA1LzEwLzA2n1SBaAAAABx0RVh0U29mdHdhcmUAQWRvYmUgR 
mlyZXdvcmtzIENTM5jWRgMAAAQRdEVYdFhNTDpjb20uYWRvYmUueG1wADw/eHBhY2tldCBiZ 
Wdpbj0iICAgIiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+Cjx4OnhtcG1ldGEge 
G1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDQuMS1jM 
DM0IDQ2LjI3Mjk3NiwgU2F0IEphbiAyNyAyMDA3IDIyOjExOjQxICAgICAgICAiPgogICA8c 
mRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5b 
nRheC1ucyMiPgogICAgICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgI 
CAgICB4bWxuczp4YXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iPgogICAgICAgI 
CA8eGFwOkNyZWF0b3JUb29sPkFkb2JlIEZpcmV3b3JrcyBDUzM8L3hhcDpDcmVhdG9yVG9vb 
D4KICAgICAgICAgPHhhcDpDcmVhdGVEYXRlPjIwMDctMDktMDhUMjI6Mzg6MzhaPC94YXA6Q 
3JlYXRlRGF0ZT4KICAgICAgICAgPHhhcDpNb2RpZnlEYXRlPjIwMDctMDktMDhUMjI6NDg6M 
zRaPC94YXA6TW9kaWZ5RGF0ZT4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgICAgIDxyZ 
GY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICAgICAgICAgIHhtbG5zOmRjPSJodHRwO 
i8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyI+CiAgICAgICAgIDxkYzpmb3JtYXQ+aW1hZ 
2UvcG5nPC9kYzpmb3JtYXQ+CiAgICAgIDwvcmRmOkRlc2NyaXB0aW9uPgogICA8L3JkZjpSR 
EY+CjwveDp4bXBtZXRhPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI 
CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI 
CAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI 
CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI 
CAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI 
CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgII5AjlkAAAEsSURBVFiF3 
ZfRDsIgDEVZKWV0sIGT2f//UgtG/QJj0vNCeOvJ5Wadc5NU6mmPWy3svqS97TnZg/PejvS23 
I6cKHh7YKCU6/a2ZPIAi0EAPPExPdc9BVyPu9z3+O+xfgCEtI93Wzggdxl0/vdQPwACFw2zE 
KxdGgdu0k3mSSW5LeFySMPR2CaHxYpiyi4TLF3S0IMk3aImkGoGWERw3lHE/3mkXwAhu00NN 
c151zQtai64OVbNKnU+Wj1tavLU5EvqCmuVB1vs5tBM2k0s1/xuPh43i2nqhuBiBD231qW3f 
F43g3FCXHU/QABAijFSoPNq5jwB1VLj9HPH9X7YGvT0MY7dPdKnkLrQn5cxTU/x9SemnsbUv 
sDH0jmKwaSoVjHEj+UQJUJ7EEV6+T0B4Yo20scg/HAAAAAASUVORK5CYII="); 
}

Probablemente éste sea el momento en el que muchos digan ¡¿qué?!… pues bien, el objetivo de esta técnica es insertar directamente toda la información de una imagen en la misma hoja de estilo.

¿Cómo incrustar una imagen en una hoja de estilo?

Si se fijan en el código anterior, hay cuatro partes fundamentales: en primer lugar, la referencia a la imagen parte como todas: url(, pero después de eso viene lo importante:

  1. El tipo de URL no es http://, sino data:
  2. En segundo lugar, debemos indicar el tipo de contenido o mime type, en este caso, image/png para una imagen en formato PNG
  3. Tercero: cómo está codificada la información… claro, no se trata de abrir la imagen con el bloc de notas y pegar en binario… no, no, no. En este caso, se ha utilizado base64… la verdad es que no tengo ni idea si habrán otras codificaciones compatibles
  4. Finalmente, la información de la imagen, codificada; para codificar un archivo en base64 pueden hacerlo a través de este codificador/decodificador Base64 Online. Como resultado, tendrán el mismo archivo que subieron pero representado de otra forma, pero codificado en [base64->@wiki]

¿Para qué incrutar una imagen en una hoja de estilo?

Como ponía al principio, esta no es una práctica muy generalizada, pero en ocasiones podría ser bastante recomendable, como cuando el acceso a las imágenes se podría ser limitado o sea necesario distribuir solamente un archivo auto-contenido —es el caso del tema para Google Reader desarrollado por Jon Hicks (de donde saqué la imagen del ejemplo), o algunos de los estilos de userstyles.org.

Otra posibilidad es que sea necesario reducir la cantidad de peticiones HTTP al servidor, o en situaciones en que el tamaño de las imágenes es tan reducido que la demora en hacer las peticiones podría efectivamente reducirse al incrustar las imágenes en el código.

Desventajas

A pesar de ser una buena alternativa en ciertas situaciones, este método tiene ciertas desventajas que debemos analizar antes de aplicarlo:

  • Nuestro navegador preferido, MSIE 6, no tiene soporte para url("data:… lógico, ¿acaso alguien esperaba otra cosa? Ante esto, una buena solución podría ser servir las imágenes en hojas de estilos separadas de la principal (lo que de todos modos siempre es una buena idea), y mediante comentarios condicionales determinar qué navegador recibe qué hoja, es decir:
    1. Tener una hoja “normal” con todos tus estilos, menos los estilos que reemplazarás por imágenes incrustadas
    2. Una hoja con las imágenes incrustadas, para Firefox, Opera, Safari, etc.
    3. Una hoja con los estilos de las imágenes incrustadas referenciadas con el método tradicional background:url(ruta/imagen.jpg); para Explorer
  • El tamaño de las imágenes aumenta en alrededor de un tercio (multiplica el tamaño por 4/3)
  • A menos que combines y planees inteligentemente tus selectores, si utilizas una imagen más de una vez tendrías que declararla todas las veces que sea necesario… por eso es mejor ser inteligente y planificar 😛

Y para terminar… Dean Edwards afronta el problema de la falta de soporte para Base64 en Explorer en dos artículos que no está demás reseñar:

Y aclaro (antes que alguien me lo indique en los comentarios) que esta técnica en realidad parte en HTML, y todo a lo que me he referido como “incrustar imágenes” pueden encontrarlo en inglés buscando por data URL, como en Using Data URLs Effectively with Cascading Style Sheets.