¿como mejorar el rendimiento de un sitio web?

En estos días, después de terminar la beta de mi nuevo sitio para compartir archivos FileS3, me puesto en teoría mis conocimientos sobre optimización de paginas webs (para mejor rendimiento, nada que ver con el SEO, esa es otra rama). Mi motivación para escribir este post es que hay varios sitios webs en el Paraguay con mucho trafico (o al menos eso dicen) , y todos ellos desoptimizados. Esta desoptimización es costosa para la empresa que gasta millones en hardware (supongo… no trabajo para ninguna) y en ancho de banda, y para el usuario ya que el tiempo de respuesta no es optimo.

Archivos estáticos (*.png, *.jpg, *.js, *.css, etc)

Aunque Apache es un gran servidor web, perfecto para todas las consultas dinámicas (mod_php, mod_*), consultas CGI, pero es demasiado costosa para procesar archivos estáticos, con esto no estoy diciendo que tener archivo estáticos con Apache este mal, solamente digo que si quieren ahorrar recursos para recibir mas visitantes se debe cambiar esto.

Para eso existen varios servidores “lightweight”, yo recomendaría NGinx (se lee engine x) pero hay varias alterativas, solo basta googlear un poco (también leí mucho sobre el http://www.lighttpd.net/).

Si se pregunta porque estos servidores “lightweight” son mejores que Apache para archivos estáticos, la respuesta es que estos servidores usan hilos (o métodos similares) para atender a varias consultas en simultaneo, ademas cuenta con una arquitectura mucho mas simple y pequeña, lo que se adecua a las necesitas de los archivos estáticos, ya que no necesitan nada mas que ser leídas del disco duro y ser enviada al cliente.

Otra cosa muy importante para ahorrar ancho de banda, es el cacheo en el cliente. Este cacheo es simplemente que el servidor le dice al browser (claro, a firefox casi siempre) que estos archivos estáticos no cambiaran en un tiempo dado (10 días, 20 días, 10 meses, etc), entonces el browser una vez que obtiene los archivos no vuelve a bajar el mismo archivo ya que sabe que ese archivo no va a cambiar, y tiene una copia del archivo en el disco. Para realizar esta configuracion con el NGinx pueden leer esta pagina, si usan otro servidor deben buscar como manipular el header “Expires”.

Seguramente se estarán preguntando, que pasaría si actualizamos algún archivo estático, bueno, el browser no se enteraría mientras que no se alcance el tiempo de validez del cache. Pero no se alarmen, existe una solución bastante simple, un poco de orden todo lo arregla.

Ya que nuestro contenido dinámico no se debe cachear (o se debe cachear por un tiempo mínimo como 10 min, 1h ,etc), y ya que el contenido dinámico incluye, es la que incluye a el contenido estático, nuestro problema esta resuelto!, simplemente tendremos que crear un sistema rudimentario de versionamiento. Ejemplo:

Esta es la manera como una pagina incluye a un elemento estático, un archivo css:
<link rel='stylesheet' href='/style.css' type='text/css' />

Con el versionamiento seria algo asi:
<link rel='stylesheet' href='/css/1/style.css' type='text/css' />

Entonces cuando cambiamos algo, simplemente tendremos que crear una
nueva carpeta y actualizar las referencias de la pagina hacia el elemento
estatico (si es una web en serio organizada no seria un problema).
<link rel='stylesheet' href='/css/2/style.css' type='text/css' />

Como ven, no es una solución complicada y así no tenemos excusas para utilizar el cacheo de archivos y así empezar a ahorrar un montón de ancho de banda.

Otro tip importante, para ahorrar ancho de banda es utilizar algún módulo del servidor que pueda comprimir los archivos antes de enviar (si el browser soporta). Aquí está un ejemplo de configuración para el NGinx que tiene activado la compression (gzip).

Consultas dinámicas. (*.php, cualquier otro)

Bases de datos…

En esta sección hay mucho para hablar, yo me voy a enfocar en lo mas conozco, y en lo que pienso es en donde mayor desoptimización hay, las bases de datos.

La base de datos es un mal necesario, digo mal, porque es bastante costosa, pero no podemos vivir sin ella, es costosa porque hay autenticación (casi siempre, excepto SQLite), comunicación por la red (casi todos), compilación del código SQL, ejecución del código y acceder al disco duro. Pero como dije antes, no podemos vivir sin el, entonces la solución es evitarlo cuando sea posible, para eso podemos cachear el resultado de una consulta con un TTL (time to live) apropiado. El medio de almacenamiento puede ser un archivo en el disco duro, aunque es costoso el acceso al disco duro hoy en día los sistemas operativos tienen en memoria RAM los archivos que son leídos frecuentemente. Cuando hay varios servidores webs, la mejor alternativa seria guardar el cache en un servidor de Memcached, así no se duplica el cache.

Para ayudar a todo que es cacheo, en mi opinión, se debería construir un Capa de abstracción entre el acceso a base de datos y la aplicación (similiar al PDO del PHP5) y ahí implementar todo lo que sea cache. Ahora mismo estoy escribiendo un lenguaje de modelado de datos y su generador de código. Su misión sera que genera las tablas y el código PHP (mas adelante python, java,etc) para acceder a la tablas sin escribir SQL (similar a www.metastorage.net) y ahí estará implementado cacheo de consultas a disco duro, memcached o cualquier otro medio. Más adelante estaré hablando mas sobre este proyecto, que espero que muy pronto este todo terminado.

Minimizando el tráfico.

Al igual que los archivos estáticos, los archivos dinámicos pueden ser cacheado y comprimidos. Claro que el TTL tiene que ser un tiempo razonable. Imagínense la página principal de un diario digital, esa página si tiene último momento, no cambiaría tan frecuentemente, cambiaría a cada 5 minutos o 10 minutos, entonces para que generar toda la página para cada visitante?. La solución sería (para los usuarios que no este logueados, si soporta logueo) cachear toda la página (en el disco duro) y comprimir así tenemos dos versiones del mismo cache, la comprimida y la descomprimida, porque comprimir “on the fly” es costosa, se pierde tiempo y valiosos ciclos de CPU. Para cada visitante enviamos el cache comprimido si soporta o el cache normal y también le enviamos el tiempo que falta para que el cache sea recreado (para un diario yo pondria 5 ~ 10 min). Y para los artículos que generalmente no cambian creo que un TTL adecuado es de 2 horas o algo así.

Hace algún tiempo escribí una clase que hace eso mismo, gCache.

APC.

APC (Alternative PHP Cache) es un excelente cache en RAM del bytecode del PHP (PHP no es interpretado desde la versión 4). Para demostrar la diferencia, hice una simple prueba con la pagina principal del  FileS3, cuya pagina esta bien optimizada ya que cuenta con un cache y en el momento de la prueba el cache existia, lo que significa que solo una par de lineas de códigos PHP fueron ejecutadas, ademas fue probada en el servidor para que no haya retardo de red.

Para la prueba utilice ApacheBench (ab -c 30 -t 30 http://local.files3.com/) simulando a 30 usuarios concurrentes que visitaban a http://local.files3.com/ por 30 seguntos.

This is ApacheBench, Version 2.0.40-dev <$Revision: 1.146 $> apache-2.0
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Copyright 2006 The Apache Software Foundation, http://www.apache.org/

Benchmarking local.files3.com (be patient)

Server Software:        Apache/2.2.8
Server Hostname:        local.files3.com
Server Port:            80

Document Path:          /
Document Length:        7232 bytes

Concurrency Level:      30
Time taken for tests:   30.8135 seconds
Complete requests:      3166
Failed requests:        0
Write errors:           0
Total transferred:      23656585 bytes
HTML transferred:       22918208 bytes
Requests per second:    105.50 [#/sec] (mean)
Time per request:       284.347 [ms] (mean)
Time per request:       9.478 [ms] (mean, across all concurrent requests)
Transfer rate:          769.86 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0  10.1      0     117
Processing:    19  281  75.0    282     675
Waiting:        1  248  75.2    252     653
Total:         20  282  74.0    283     675

Percentage of the requests served within a certain time (ms)
  50%    283
  66%    302
  75%    316
  80%    328
  90%    364
  95%    408
  98%    472
  99%    499
 100%    675 (longest request)

Como verán, con toda la optimización pudimos responder 3166 paginas. Ahora instale APC ( yum install php-pecl-apc.i386  -y), y la configuración  de facto, realice la misma prueba y aquí están los resultados.

This is ApacheBench, Version 2.0.40-dev <$Revision: 1.146 $> apache-2.0
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Copyright 2006 The Apache Software Foundation, http://www.apache.org/

Benchmarking local.files3.com (be patient)

Server Software:        Apache/2.2.8
Server Hostname:        local.files3.com
Server Port:            80

Document Path:          /
Document Length:        7244 bytes

Concurrency Level:      30
Time taken for tests:   30.1087 seconds
Complete requests:      7028
Failed requests:        0
Write errors:           0
Total transferred:      52570787 bytes
HTML transferred:       50932564 bytes
Requests per second:    234.26 [#/sec] (mean)
Time per request:       128.064 [ms] (mean)
Time per request:       4.269 [ms] (mean, across all concurrent requests)
Transfer rate:          1711.20 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   4.2      0      69
Processing:    39  126  23.1    123     300
Waiting:        1  123  22.7    120     300
Total:         39  127  22.9    123     300

Percentage of the requests served within a certain time (ms)
  50%    123
  66%    130
  75%    136
  80%    141
  90%    154
  95%    169
  98%    191
  99%    206
 100%    300 (longest request)

El resultado es asombroso, ahora pudimos servir 7028 paginas, con solamente haber instalado APC, si pasan un poco mas de tiempo tuneando, podríamos mejor mucho mas.

Tu experiencia!

Seria espectacular si ustedes comparten aqui sus metodos de optimizacion para ayudarnos entre todos, varias cabezas son mejores que una.

Saludos.

9 Comments

  1. vivi marandarí says:

    tremendo post Cesar! wow.. 3862 paginas servidas más con APC!!! y solo 0.7048 segundos de Time taken for tests .. de diferencia.. :$ :omg:

  2. César Rodas says:

    Si… no sabia esa diferencia ya que nunca hice un “benchmark” antes solo había instalado en mi servidor y se quedo ahí, luego se me ocurrió instalar y probar en mi servidor de desarrollo, y me di cuenta de la diferencia que hay,

  3. Pablito says:

    Guau! excelente articulo, ya estoy instalandome el ApacheBench para jugar :D

    Tocando el tema, se me ocurre que q estamos hablado de una politica tipo: “si el archivo no fue leido en la ultima hora eliminarlo del cache”. Tonces te quedas en tu cache con solamente lo q se esta solicitando y te centras en servir y actualizar solamente estos archivos.
    Por ejemplo solo una portada q cambie y sea muy requerida jamas saldria del cache y dejarias de trabajar sobre ella cada vez que un cliente la solicite. La actualizarias solamente cada digamos… 15min. El consumo se me hace bajaria bastante y seria autosostenible. Trabajaria bajo una politica “bajo demanda”.

    Ahora a leer sobre el ApacheBench :D

  4. Samuel Giubi says:

    Es increible como muchos Webmasters no saben o no quieren ser parte en su vida en Web a alguna utilidad de cache, entre las miles conocidas y ofrecidas.-

    Primero que todo, excelente post César, como siempre. En segunda hace ver la disponibilidad y la potencia de estas herramientas de caché. Yo en mi Server Proxy nomas luego no podría ni pensar en vivir sin mi cache sobre squid, cómo un Webmaster si?, y a qué me refiero?, a el famoso “Efecto Menéame” entre otros, cuando tu sitio es visitado por una gran cantidad de usuarios -al mismo tiempo- y varios blogs, caen .. o bien también en lo que fue la presentación de FriendFeed (vease: http://www.genbeta.com/2008/11/16-imagen-de-la-semana-problemas-del-directo ) es decir, WTF!? .. y este es un post mas que demuestra la potencia de estas herramientas.-

    En cuanto a probar con apache bench sobre un sitio online, tiene sus resultados que puede llegar a variar, apache bench es recontra conocido por ofrecernos estos tipos de pruebas en piloto, pero lo mas lindo es probarlo en un server paralelo para mi gusto. @Pablito, puedes instalar xampp sobre Windows que creo que ya viene incluido como herramienta complementaria.-

    Saludos e interesante el APC.-

  5. Matías says:

    El nginx es excelente para el contenido estático y para balanceo de carga.
    Para el desarrollo con Ruby On Rails generalmente se utilizan múltiples servidores corriendo tu aplicación web.
    En el caso de Apache se configura el mod_proxy para que haga un “load balancing” entre los distintos servidores de tu aplicación (escuchando en puertos diferentes –por ejemplo: 8000, 8001, 8002–).
    Con nginx es posible realizar lo mismo que hace el mod_proxy de Apache, por ello cada vez hay más sitios que se cambian al nginx por cuestiones de facilidad.

  6. César Rodas says:

    Ahora mismo se quiere mucho al NGinx, sitios grandes usan como YouTube, WordPress (le pregunte a Matt Mullenweg y me confirmo).

    También escuche algunas cosas sobre el Lighttpd (www.phpclasses.org lo utiliza para consultas estáticas).

    Personalmente me quedo con el NGinx, pero es cuestión de gustos.

  7. Matias says:

    Si.
    Incluso WordPress llegó a utilizar la versión en desarrollo “no estable” de nginx.

  8. [...] En estos días me he dedicado a ahondar en temas de performance para sitios relativamente grandes para shared hostings, y pequeños para servidores dedicados (posiblemente tenga que hostear un periodico local con 50.000 visitas diarias, todavía no está confirmado). Aunque el título de este post suena un poco marketinero, en realidad este artículo será la continuación de como mejorar el rendimiento de una web. [...]

  9. Nicolas says:

    Programe un diario digital para la ciudad de la plata.. y decidi hacer un mini sistema de cacheo propio, capturando los buferes de salida de php y guardandolo en el el disco.
    Entonces cuando se solicita una pagina, primero se chequea si esta en la cache y sino se genera nuevamente.
    Como es un diario digital cada ves que alguna noticia es actualizada la cache se vuelve a generar…..
    Con esto logre bajar los accesos a la BD en forma exponencial… pero no se si sera la mejor solución….

    saludos

Leave a Reply