Cómo servir 12 millones de páginas con WordPress sin morir en el intento

Una de mis partes favoritas de mi trabajo en thenextweb.com es cuando me pongo el sombrero de devops para conseguir que la web sea lo más rápida y estable posible. Afortunadamente, en el último año el blog ha crecido hasta alcanzar más de 10 millones de páginas vistas al mes, por lo que la tarea de escalar la web para que pueda soportar todo este tráfico ha supuesto un interesante reto desde el punto de vista tecnológico.

Wordpress

Como vais a ver, no hace falta ser ningún crack ni tener arcanos conocimientos de programación en código máquina para escalar un blog de WordPress. Voy a explicar lo más brevemente posible la arquitectura del sitio y dar algunas pistas que, según mi experiencia, funcionan para conseguir un back-end para WordPress estable y capaz de soportar cientos de miles (o millones) de visitas al mes.

La arquitectura del blog

The Next Web no es más que un blog en WordPress optimizado hasta la extenuación, con un tema y varios plug-ins personalizados y desarrollados internamente. La aplicación corre en 2 servidores: un servidor de base de datos con MySQL y Solr para las búsquedas + un servidor web con Apache, PHP+APC, Memcached y Varnish. Por supuesto toda configuración es mejorable, así que espero oír vuestras opiniones y sugerencias en los comentarios! Estos son 8 puntos que destacaría de nuestra configuración:

1. Usa un CDN para servir archivos estáticos

Un CDN puede aligerar mucho la carga de tu servidor, ya que permite no malgastar recursos en servir imágenes y otros archivos estáticos, dejando un mayor porcentaje de CPU al procesamiento de scripts dinámicos de PHP. Si bien hay servidores como nginx que pueden ser más eficientes que Apache en estos menesteres, un CDN sigue siendo una buena idea que deberías comenzar a usar de cualquier forma, antes incluso de que tu web comience a tambalearse por el tráfico.

Una forma muy sencilla de comenzar a servir todas las imagenes de tus posts desde el CDN es añadir un filtro a la función the_content en el functions.php de tu tema y reemplazar la ruta a la imagen por la de tu CDN. Por ejemplo:

add_filter( 'the_content', 'cdn_image_replacer' );
function cdn_image_replacer( $content ) {
	$content = str_replace( 'http://'.$_SERVER['HTTP_HOST'].'/files/', 'http://cdn.thenextweb.com/files/', $content );
	$content = str_replace( 'http://'.$_SERVER['HTTP_HOST'].'/wp-content/blogs.dir/', 'http://cdn.thenextweb.com/wp-content/blogs.dir/', $content );
	return $content;
}

Por supuesto, siempre debes permitir cachear todos los archivos estáticos en el cliente, con un tiempo muy largo de expiración de la caché siempre que sea posible. En todo caso sigue las recomendaciones de Google PageSpeed o YSlow, disponible como extensión para varios navegadores. Tampoco está de más estudiarse algún manual como el excelente Caching tutorial de Mark Nottingham. Estas simples lineas en tu .htaccess son un buen comienzo para configurar decentemente la caché en el cliente.

Header unset ETag
FileETag None

<FilesMatch "\.(js|css|swf|pdf|flv|ttf|woff|eot|svg)$">
	Header set Cache-Control "max-age=360000, public"
</FilesMatch>

<FilesMatch "\.(ico|pdf|flv|jpg|jpeg|png|gif|js|css)$">
	Header unset Last-Modified
	Header set Cache-Control "public, max-age=864000, must-revalidate"
	ExpiresDefault "access plus 2 months"
</FilesMatch>

2. Ten cuidado con los plug-ins que instalas

El directorio de plugins de WordPress es extensísimo, y aunque hay auténticas maravillas, también hay código que deja mucho que desear en cuanto a eficiencia y diseño. Existen plugins que pueden funcionar bien en una página con unas pocas visitas al día, pero sus consultas a la base de datos te pueden tirar un servidor frecuentado en cuestión de segundos.

Mi consejo es que antes de instalar cualquier plugin, te tomes tu tiempo para estudiar cómo funciona, evaluar si realmente hace lo que buscas y si podrías implementar la solución tú mismo de forma más eficiente. A la larga vas a salir ganando y te ahorrarás dolores de cabeza buscando el culpable de que tu página se caiga o tarde varios segundos en cargar.

3. Monitoriza tus servidores con Munin

Munin screenshot

Munin es una herramienta open source de monitorización muy fácil de configurar y que es de gran ayuda para saber qué ocurre en tu servidor e identificar fallos de configuración o cuellos de botella según la tendencia ascendente o descendente de las gráficas, lo que sirve para prevenir problemas actuales y futuros. Además hay innumerables add-ons de Munin que permiten monitorizar prácticamente todos los procesos que corren en el servidor (MySQL, I/O, Memcached…).

4. Análiza el código de la aplicación con xhprof

xhprof es un módulo de PHP desarrollado originalmente por Facebook que sirve para analizar el rendimiento de un script PHP mostrando diversos parámetros, cómo el tiempo de ejecución y la memoria consumida. Una vez instalado puedes consultar el sencillo interfaz web que incluye la aplicación para ver qué funciones en tu programa tardan más en ejecutarse.

xhprof screenshot

Esta interfaz muestra la traza de la petición, indicando todas las funciones que se llaman, con qué frecuencia, y cual es el impacto al rendimiento total en cuanto a tiempo de ejecución y la memoria consumida. Estos informes son muy útiles para detectar posibles puntos de optimización, que repercutirá en un mejor aprovechamiento de los recursos del servidor.

5. No descuides tu base de datos

No pierdas de vista tu log de consultas lentas y tus variables de configuración de MySQL, sobre todo todas las relacionadas con la caché de consultas y el número de conexiones. A medida que tu blog crece en visitas y número de artículos, es probable que lo que ayer era una configuración correcta, hoy se quede pequeña para tus necesidades. También puedes encontrarte con plug-ins que hacen consultas sobre campos sin índice, por lo que deberías analizar dichas queries y añadir los índices necesarios si se diera el caso.

Otro truco: WordPress, por defecto, no elimina las revisiones en la tabla de posts de la base de datos. Esto no es un problema para blogs con pocos posts, pero en un blog grande como The Next Web, con más de 40.000 artículos publicados, podríamos encontrarnos fácilmente con 3 o 4 veces este número solo en revisiones antiguas, lo que supone un uso innecesario de espacio en la base de datos. En mi experiencia, es posible ahorrar varios milisegundos en cada consulta a la tabla wp_posts eliminando todas las revisiones más antiguas de 3 meses, por ejemplo.

SELECT *
    FROM wp_posts a
    LEFT JOIN wp_term_relationships b
    ON (a.ID = b.object_id)
    LEFT JOIN wp_postmeta c
    ON (a.ID = c.post_id)
    WHERE a.post_type = 'revision' AND a.post_modified < (DATE_SUB(CURDATE(), INTERVAL 3 MONTH))

6. Ciclo de desarrollo rápido y control de versiones

Este punto no está directamente relacionado con la escalabilidad pero si con la estabilidad y fiabilidad que una web con mucho tráfico debe siempre ofrecer. Para empezar, es imprescindible mantener el código de la aplicación en un sistema de control de versiones como Git, para tener plena libertad para modificar el código y poder volver a una versión anterior siempre que quieras, trabajar paralelamente en diferentes funcionalidades o compartir el código con otros programadores de forma sencilla.

Capistrano, una herramienta de la que he hablado anteriormente en el blog (Una introducción a Capistrano), es una forma excelente de gestionar el código en producción: En TNW lo usamos para poner código en live pulsando un botón y para volver fácilmente a una versión anterior del código en caso de desastre. Esto nos permite pasar el código de desarrollo a producción de forma eficiente, permitiendo arreglar bugs e introducir nueva funcionalidad en cuestión de segundos.

7. Cachealo todo con Memcached y Varnish

Para el final he dejado lo más importante, en términos de mejora de rendimiento. Si a pesar de todas las optimizaciones el blog sigue renqueante, y una vez que hemos intentado solucionar el problema con más y mejor hardware, es hora de sacar la artillería pesada en forma de más y mejor sistemas de caché.

En TNW usamos 2 capas de caché a alto y bajo nivel: Varnish y Memcached. Estas dos aplicaciones son las que soportan la gran mayoría del tráfico en el sitio.

  • Memcached lo usamos para almacenar el resultado de muchas consultas, ya sea por ser más lentas, o bien porque se ejecutan con mucha frecuencia, como pueden ser los objetos de tipo Term (categorias y tags), punteros a los posts anteriores y posteriores de cada artículo, o la lista de artículos relacionados. Tambien guardamos fragmentos de HTML listos para usar. Por ejemplo, cada post individual que forme parte del loop se carga desde memcached.
  • Varnish es un acelerador web que funciona como un proxy reverso. Se situa entre el cliente y tu servidor web y según como lo configuremos, permite usarlo de muy diversas maneras, por ejemplo como balanceador de carga (repartiendo las peticiones entre varios back-ends). También permite servir las página desde caché o desde el back-end según la URL o las cabeceras HTTP de la petición. Varnish guarda páginas completas en memoria, y también permite guardar fragmentos en HTML, que son ensamblados a posteriori por el propio Varnish. En TNW lo usamos desde hace un tiempo y se ha convertido en una pieza imprescindible en nuestra arquitectura.

Otra solución alternativa para cachear páginas es usar alguno de los muchos plug-ins de caché de WordPress, cómo W3 Total Cache o WP Super Cache. Estos plugins guardan una caché de las páginas generadas en disco para después poder servir las sucesivas llamadas usando esta caché con la ayuda de un puñado de reglas en el .htaccess. Personalmente este tipo de plug-ins no me han terminado de convencer cuando los he probado, pero por si acaso los pongo como alternativa.

8. Optimizaciones a nivel de Sistema Operativo

También es posible que llegado cierto punto, el Sistema Operativo se convierta en el cuello de botella. En caso de ser necesario, no está mal revisar la configuración del mismo y asegurarse que ciertas variables del sistema, en especial las relacionadas con las conexiones de red, tienen valores adecuados para soportar suficiente tráfico.

Por ejemplo, algunas variables importantes son:

  • net.core.somaxconn (define el límite de usuarios en la cola de espera para iniciar una nueva conexión TCP)
  • net.ipv4.tcp_tw_reuse (permite al kernel reusar conexiones TCP cuando aún no han expirado, aliviando la carga en el servidor), o
  • net.ipv4.ip_local_port_range (define el rango de puertos usables por el sistema.

Estos 8 puntos creo que resumen lo más importante de un año escalando WordPress, y creo que se podrían aplicar a otro tipo de aplicaciones web. Es una visión muy general pero espero que sea útil para todos. ¡Dadme vuestra opinión en los comentarios!

Lee ahora: No olvides tu hoja de estilo CSS para impresión »

  • http://twitter.com/javipas JaviPas

    Un artículo brutal Pablo, me ha encantado aunque he echado de menos aún más detalles. La gran sorpresa es la de que uséis Apache y no Nginx, cuando en todas partes he leído -y esa era mi experiencia hasta ahora- que por el propio diseño de WordPress Apache no era la mejor opción con tanta conexión concurrente.

    Otro detalle curioso es la ausencia de plugins de caché, que parecen requisito imprescindible en otros sitios web en los que se habla de mejorar el rendimiento de WordPress. W3 Total Cache como dices es el más popular porque parece gestionar muy bien el tema del CDN o la caché de objetos y de páginas con Memcached/APC (por lo que veo, vosotros lo solucionáis directamente toqueteando el functions.php. En los “Muys” también usamos Varnish (aunque no W3, a mi tampoco me ha convencido y tiro de plugins menos ambiciosos) y me pregunto si esa ayuda extra no sería buena también en The Next Web.

    Por cierto, nada de APC? Supongo que no podrás dar detalles, pero los servidores de TNW deben ser bastante potentes, imagino…

    Lo que te digo, un artículo genial, aunque me haya quedado con ganas de mucho más. Cada uno de los ocho puntos daría para un post rico rico ;)

    Saludos!

    • Pablo Román

      Gracias por el comentario Javi! Por partes:
      - Usamos Apache por que es lo que conocemos mejor, y si hay cualquier problema es más facil “meter mano”. Es un poco comodón admitirlo, pero si algo funciona, mejor no tocarlo :)
      - Los plugins de caché como W3 nos han dado problemas cuando generaban archivos corruptos que directamente nos tiraban el servidor (Varnish no sabía que hacer con ellos y reventaba). La verdad es que no hemos notado mucha diferencia usando plugins, si tienes Varnish son un poco redundantes.
      - Usamos APC, pero me sonaba a algo tan común que me lo he saltado :) . Lo especificaré en el artículo.

      Tengo pensado escribir algo más concreto sobre Varnish y otras optimizaciones al código de WP, el tema da múuucho de sí, pero no es cuestión de aburrir con posts demasiado largos, jaja.

      Saludos!

      • http://twitter.com/javipas JaviPas

        Desde luego a lo de no tocar si funciona ;) pero mi experiencia con Apache fue malísima cuando empecé -entonces no sabía nada de Varnish- y la pila LEMP parecía la solución por lo que leía entonces.

        Si no recuerdo mal, configurar Varnish bien para que no haya problemas con W3 , WP Supercache y otros era cuestión de afinar con el default.vcl, pero VCL es una especie de mundillo en sí mismo no crees? Lo de que sea redundante sí me extraña, la verdad, pero supongo que habréis hecho vuestras pruebas de rendimiento y si al final da más problemas que otra cosa, fuera, desde luego.

        Ya suponía yo que lo del APC lo tendrías controlado. En cuanto a futuros posts aburridos, por favor, abúrrenos, abúrrenos. Me encanta el tema y es muy difícil encontrar cosas chulas en español.

        Ya que estamos: habéis probado mod_pagespeed? En Apache promete mucho, en Nginx siguen trabajando en él y aún no lo he querido probar porque les queda aún algo de camino…

        No me quiero enrollar mucho, que me surgen miles de preguntas y comentarios. Lo dicho. Dale duro a los posts aburridos esos, yo acabo de enlazarte en Incognitosis porque me has venido al pelo para el post de hoy :)

  • Pingback: Cómo servir 12 millones de páginas web con WordPress y no morir en el intento

  • Pingback: Web Performance Optimization. ¿Qué es mucho más importante que el diseño? La velocidad | Incognitosis

  • Pingback: La semana en los blogs CCLXXXV - Error 500

  • Pingback: La semana en los blogs CCLXXXV

  • Carlos

    Maravilloso artículo, muy útil. Te agrego a mi lector de feeds.

  • Pingback: maillot de foot

  • Pingback: maillot de foot

  • Pingback: erreur maillot marseille

  • http://maillot-defoot-pascher.blogspot.com/ maillot de foot

    I’ll apply this idea…… It can be fun!

  • Joefay

    por ahí leí que poner la configuración del .htaccess directamente en el archivo httpd.conf de apache da mejor rendimiento para que se lea una sola vez, asi se quita el .htaccess y no se tienen que leer este archivo en cada petición, aunque si se usa varnish desconozco si esto sea relevante. Excelente articulo.

    • Pablo Román

      Gracias, en efecto también se puede mover la configuración al httpd.conf. Buen apunte!

  • sergiom23

    Brutaalll!, me encantó lo de Solr, Un genio Pablo un Genio!

  • luis

    Hola Pablo.

    Tengo una duda con memcached, actualmente estoy haciendo una webapp, pero quiero dejar listo el sistema de cacheo para salir a producción, mi duda es. Hay inconveniente en cachear todas las publicaciones que los usuarios hagan en la plataforma? es una plataforma similar a pinterest pero con mas contenido en las publicaciones, es recomendable cachearlas todas en memoria para evitar consultas a DB?

    Gracias desde ya

    Saludos!, buen post

  • Vicente

    Muchas gracias por tu artículo, he aprendido un montón. Espero ponerlo a prueba pronto.