Optimización de carga web DNG Photo Magazine

Tiempo de carga DNG Photo Magazine

Gran parte del trabajo que realizamos en DNG Photo Magazine, además de las tareas de administración, contactos, creación de contenidos, maquetación de la revista, etc… también pasa por mejoras en la web, ya sean visuales o como en la mayor parte de los casos, cambios en el backend que no tienen repercusión gráfica en lo que veis los usuarios al visitar la web, estos cambios, a veces mínimos, son mucho más frecuentes de lo que podría parecer.

Hace unas semanas, asistí a unas charlas en Lugo, Netlab, mi tema era «Gestión de negocio digital», el caso es que en un descanso estuve hablando con Alvaro Fontela, Co-Fundador de Raiola Networks y me recomendó el cambio del CDN de imágenes de Photon a CloudFlare, lo que me hizo ponerme a pensar un poco en el tema.

Photon es un servicio de CDN para imágenes de Automattic (los creadores de WordPress), que está incluido en el plugin Jetpack. Photon es un gran servicio, con muchas cualidades y tan fácil de implantar como activarlo en el el plugin de Jetpack, así de fácil, el sólo se encargará de cambiar las urls de nuestras imágenes para servirlas directamente desde su CDN, pero ya hace tiempo que estaba pensando en alternativas porque a pesar de ser un servicio excelente, cuando lo que necesitas es más flexibilidad… entre las cosa que no me acababan de convencer es que una vez que Photon realiza caché de una imagen, lo hace para siempre, sin posibilidad de invalidarla, a no ser que le cambiemos el nombre. La segunda cuestión que no me convencía eran los parámetros que le añade a las imágenes (query strings), para el tamaño, si debe rediminseionar la imagen, recortarla, etc…

Lo primero que hice fue crear tres sudbominios para servir las imágenes desde Cloudflare, algo sumamente sencillo ya que tenemos los DNS’s albergados en CloudFlare ya que por los tiempos de diversas mediciones son una de las alternativas más rápidas en cuanto a tiempos de resolución. Lo dicho, creamos tres subdominios tipo A y los apuntamos a la misma IP de nuestra web, ahora lo que hacemos es crear el sitio web para que sea servido por el servidor web Nginx, en teoría sólo vamos a servir imágenes, pero realizadas las primeras pruebas, algunos de los plugins también servían algún css desde el directorio wp-content por lo que habilitamos el nuevo sitio web para servir imágenes, css y js:

Lo primero que hacemos es apuntar el nuevo sitio web al ya existente, con la primera condición lo que hacemos es servir los archivos de imagen, fijar nuestras cabeceras personalizadas, caché y decirle al navegador que retenga una copia durante tres meses (max-age=7776000 en segundos).

A continuación habilitamos la compresión gzip para ahorrarnos algunos bytes en la transmisión de archivos y con las dos condiciones siguientes, al igual que con la primera, habilitamos los archivos css y js, ahora ya servidos comprimidos en gzip. Con la última condición lo que hacemos es denegar todos los demás contenidos, por lo que si intentamos acceder por ejemplo a un pdf, un archivo php, html, etc., el servidor nos devolverá un error 403 de prohibido.

Una vez habilitados los tres subdominios en Cloudflare para que actúe como CDN y habilitados los mismos en nuestro servidor, pasamos a cambiar la imágenes «apuntadas a mano» a Photon por el nuevo CDN, esta tarea es fácil, primero todas las imágenes de las diez ediciones del concurso DNG Photo Magazine y alguna que otra imagen de cabecera, anuncios y las fotos de Flickr. Las fotos de los concursos son archivos html estáticos generados en el servidor cada vez que se validan las fotos y que se van incluyendo desde la plantilla personalizada del concurso, en base a los parámetros de la url, por ejemplo https://www.fotodng.com/concurso-foto-dng-2015-pag-5/ que recibe dos parámetros, uno para el año del concurso y otro para la página del mismo, por lo que modificamos el código PHP para sustituir las urls de Photon por las del nuevo CDN y desde la parte de administración volvemos a crear todas los htmls de páginas de concurso, únicamente un click de botón por cada edición, diez pulsaciones y ya tenemos miles de imágenes cambiadas al nuevo CDN. Para las demás imágenes realizamos una búsqueda por todo el código para ver donde tenemos referencias directas a Photon:

# grep –include=\*.{php,js,css,txt,htm,html} -rnw ‘/ruta/sitio/web/original/html’ -e «http://i0.wp.com/» | more
# grep –include=\*.{php,js,css,txt,htm,html} -rnw ‘/ruta/sitio/web/original/html’ -e «http://i1.wp.com/» | more
# grep –include=\*.{php,js,css,txt,htm,html} -rnw ‘/ruta/sitio/web/original/html’ -e «http://i2.wp.com/» | more

Y realizamos las sustituciones. El último paso serían desactivar Photon y hacer que las imágenes se sirvan desde Cloudflare, pero ya puestos antes vamos a realizar algunas pequeñas optimizaciones más. Como CDN para js y css utilizamos Amazon CloudFront y para la gestión, minimizar y combinar archivos, etc., usamos el plugin W3TC y para minify usamos una configuración personalizada, archivo a archivo, lo que suele dar los mejores resultados pero también es una tarea tediosa. Uno de los archivos a combinar es el archivo javascript de stats de WordPress, pero observando la consola vemos que lo incluye aparte, el problema es que con las actualizaciones de Jetpack cambia el nombre de archivo consistente en un número, por lo que actualizamos el nombre del mismo y eliminamos el anterior. También observamos un error en la consola de objeto no definido en el archivo encargado de cargar el reproductor multimedia, el problema se soluciona fácilmente ya que cargaba dicho archivo antes del general de javascript (que se carga antes del cierre de body), con lo que lo incluimos en W3TC en el lugar correcto y ya tenemos la consola libre de errores y algunos bytes ahorrados y una petición menos. Además observando las herramientas de desarrollo del navegador vemos que se realiza una redirección al solicitar el archivo de Google Analytics, entramos en la cuenta de Google Analytics y observo un mínimo cambio en el código de seguimiento, lo que antes tenía //www.google-analytics.com/analytics.js ahora es https://www.google-analytics.com/analytics.js, es decir, el código anterior cargaba el archivo por http ó https dependiendo del protocolo de nuestra web, ahora si usamos la versión http nos redirecciona a la versión https, por lo cambiamos el código para evitar esa redirección y ganar unos milisegundos, aunque parezca insignificante, cada milisegundo que arañemos en la velocidad de carga irá sumando en acelerar nuestra web.

El siguiente paso es optimizar un poco más las imágenes, algunas de las imágenes como las portadas de cada número, las cargábamos desde Google App Engine. Con la idea de unificar servicios, las hemos subido a la web para servirlas desde CloudFlare, pero antes vamos a optimizarlas un poco. Todas las fotos subidas a cada entrada se ajustan en tamaño y compresión antes de ponerlas en el post y desde el servidor se optimizan sin pérdida con # jpegoptim –strip-all pero en este caso además del comando citado también queríamos realizar una optimización de las portadas con pérdida manteniendo un balance entre compresión y calidad. Las pruebas las realizamos en local con VVV, hacemos un # vagrant up (la máquina ya tiene instalado jpegoptim de una ocasión anterior) y realizamos varias pruebas con el parámetro -m desde 70 hasta 50 y como nos parece suficiente la calidad ofrecida por la compresión de 50, lo ejecutamos en todos los archivos de portada con:

# jpegoptim -p -m50 *.jpg

Ahora que ya tenemos optimizadas las imágenes nuevas subidas, vamos con algunos archivos css y jss que al no estar incluidos en W3TC se cargan aparte y WordPress les añade el parámetro de versión ?v=x.x.x y que por temas de caché no nos interesa mantener, podemos usar algún plugin para dicho menester, pero ya que tenemos el nuestro personalizado lo que hacemos es añadirle la función para eliminar dicho parámetro al cargar los archivos css y jss:

El motivo de crear tres subdominios es para realizar Domain Sharding aunque en la mayoría de los casos sólo se están utilizando dos, en breve tendremos que revisar la distribución con la adopción de SPDY  y HTTP/2 con el soporte de ilimitadas peticiones concurrentes, pero de momento además del Domain Sharding vamos a utilizar DNS Prefetch (precarga de DNS) con lo que el navegador realiza la precarga y resolución de DNS’s en segundo plano ahorrándonos otros milisegundos en el tiempo final de nuestra página, para añadir nuestros dominios ponemos:

Y ya casi hemos finalizado, nos queda cambiar las las imágenes de los posts del CDN de Photon al de CloudFlare. La primera idea era utilizar regexp para sustituir las ocurrencia de src por las nuestras personalizadas, algo similar a la sustitución de $pattern = ‘/src=»http:\/\/((www\.)?fotodng\.com\/wp-content\/uploads\/(\S)*\.(png|jpg|jpeg|gif))/i’; por la versión con CDN, pero nos encontramos con el problema de el sistema añade el tan necesario srcset para las imágenes en diferentes tamaños con lo cual la solución no era útil. La segunda idea fue crear una versión propia del código de Photon, la modificación no era complicada, pero realiza demasiadas funciones que no necesitamos (redimensionar imágenes, recortarlas, convertirlas, etc.) por lo que en la búsqueda de una opción más sencilla empezamos a investigar la opción de WordPress pre_option y aplicarla a upload_url_path y lo aplicamos sólo si no estamos en la parte de administración de nuestra web con lo que nos queda lo siguiente (lo podemos aplicar en nuestro tema, nuestro propio plugin o en nuestro caso añadirlo a las funcionalidades de nuestro plugin de administración):

Ahora simplemente desactivamos Photon y activamos el anterior código y ya tenemos las imágenes servidas desde CloudFlare. Podríamos seguir afinando aún más las optimizaciones (hay varias optimizaciones que se realizan directamente desde el servidor web, código PHP, base de datos, etc…), pero de momento vamos a dejarlo aquí. Tenemos diferentes tiempos de respuesta según el país de origen de las peticiones, los tiempos mostrados en la siguiente captura son desde Suecia, antes y después de las optimizaciones, los tiempos varían en función de varios factores, en la página inicial por ejemplo se cargan diferentes imágenes según lo que suban los usuarios a Flickr (que se descargan con el API de Flickr a intervalos definidos en cron), según la imagen de los posts en portada, etc., pero se puede observar que hemos bajado un poco el peso total de la página, realizamos dos peticiones menos de archivos externos, bajamos el tiempo de respuesta de 1,48 segundos a 745 milisegundos con lo que mejoramos el grado optimización desde 89 sobre cien hasta 94.

Optimización tiempos de carga web DNG Photo Magazine

En otra prueba (de las numerosas realizadas en diferentes servicios) con GTmetrix desde Londres,vemos los grados obtenidos en PageSpeed e YSlow además del video de carga del sitio web que os mostramos a continuación.

Tiempo de carga web DNG Photo Magazine desde GTmetrix