Optimizar rendimiento y caché en Drupal alto tráfico
Por qué tu Drupal va lento y nadie te lo dice
Drupal aguanta millones de páginas al mes. Lo que no aguanta es una instalación recién salida del horno enfrentándose a tráfico real. Cada petición dispara decenas de queries, ejecuta hooks de medio módulo contribuido y genera HTML desde cero. Te lo van a vender como un problema de hosting, pero casi nunca lo es.
Google mide Core Web Vitals como señal de ranking desde 2021. El LCP y el TTFB no se arreglan con un plugin mágico ni cambiando de proveedor cloud: dependen de lo que pase en tu backend. Un Drupal sin afinar tarda entre 800 ms y 3 segundos en responder. Bien configurado, ese mismo contenido sale en menos de 100 ms. La diferencia no es el servidor: son las capas de caché y saber qué hace cada una.
Aquí tienes lo que funciona en producción cuando el tráfico sube de verdad, sin recetas de blog genérico.
Las capas de caché que debes entender antes de tocar nada
Si vas a meter mano a la configuración sin entender cómo Drupal apila sus cachés, vas a romper cosas. Hay varias capas y cada una resuelve un problema distinto.
Internal Page Cache
El módulo Internal Page Cache del core guarda la respuesta HTML completa para anónimos. Si la página no cambió, Drupal la devuelve directamente desde la base de datos, sin reconstruir nada. Viene activado por defecto desde Drupal 8 y si lo has desactivado "por probar", actívalo otra vez antes de seguir leyendo.
Internal Dynamic Page Cache
Dynamic Page Cache cachea los fragmentos que no dependen del usuario y solo regenera las partes personalizadas. Esto importa cuando tienes muchos usuarios autenticados: bloques de sesión, carritos, mensajes del sistema. Sin esta capa, tu ratio de hit cae a niveles ridículos en cuanto la mayoría de tu tráfico está logueado.
Cache tags y cache contexts
Aquí es donde Drupal brilla y donde la mayoría la lía. La invalidación funciona por etiquetas (cache tags) y contextos (cache contexts). Cuando editas un nodo, solo se invalidan las cachés con esa etiqueta concreta. El error clásico: vaciar toda la caché con drush cr después de cada cambio porque "así seguro". Estás tirando a la basura horas de calentamiento. Aprende a confiar en los tags antes de meter Varnish.
Varnish como reverse proxy: cuándo sí y cuándo no
Para tráfico alto, poner Varnish delante es un salto cualitativo. Se sitúa antes del servidor web (Apache o Nginx) y sirve respuestas desde RAM, sin tocar PHP. Pero metido sin entender la invalidación, Varnish sirve contenido viejo y te explota en publicación.
Configuración con el módulo Purge
Purge junto con Varnish Purge permite que Drupal mande invalidaciones a Varnish cuando cambia el contenido. El flujo:
- Un editor publica o modifica un nodo.
- Drupal identifica los cache tags afectados.
- El módulo Purge envía peticiones BAN o PURGE a Varnish con esas etiquetas.
- Varnish elimina solo las respuestas marcadas con esos tags.
- La siguiente petición regenera la página y Varnish la guarda otra vez.
Bien montado, el TTFB para anónimos baja por debajo de 10 ms. Mal montado, los editores publican y no ven sus cambios hasta dentro de horas. Si esto último te pasa, no es Varnish: es que no estás emitiendo bien los tags.
Ajustes que importan en el VCL
El archivo VCL controla todo el comportamiento de Varnish. Lo mínimo viable para Drupal:
- TTL alto (24 horas o más) para contenido anónimo, y confías en la invalidación por tags. La expiración temporal es para vagos.
- Excluir
/admin/*y/user/*del cacheo. Parece obvio. Te sorprendería. - Activar el grace mode para servir contenido caducado mientras Varnish regenera por detrás. Esto te salva el día cuando publicas algo viral.
- Limpiar cookies inútiles en
vcl_recv. Una sola cookie de analytics mal configurada destroza tu hit ratio.
Redis o Memcached: deja en paz a MySQL
Drupal mete una barbaridad de datos intermedios en su caché interna: resultados de consultas, configuraciones compiladas, render arrays. Por defecto todo eso va a la base de datos (tabla cache_*), que ya tiene bastante con servir contenido real.
Mover la caché de objetos a Redis o Memcached cambia el juego. Redis suele ganar porque ofrece persistencia, estructuras más ricas y mejor soporte de cache tags. Memcached es más simple y va perfecto si no necesitas persistencia. Lo que no tiene sentido es dejar la caché en la base de datos cuando tu sitio pasa de 50k páginas vistas al día.
Configurar Redis en Drupal
Se hace con el módulo Redis y unas líneas en settings.php:
- Instala el módulo Redis y la extensión PHP (
phpredismejor quePredis; si puedes elegir, eligephpredis). - Añade en
settings.phplas líneas que definen Redis como backend de caché por defecto. - Configura el prefijo de claves si compartes instancia Redis entre varios sitios. Si no lo haces, vas a tener corrupciones cruzadas raras y vas a culpar a Drupal.
- Vigila la memoria con
redis-cli info memory. Si llenas Redis y no tiene política de eviction definida, deja de aceptar escrituras y tu sitio se cae.
Un Drupal mediano necesita entre 512 MB y 2 GB de RAM para Redis. Si lo dimensionas a 128 MB porque "total es solo caché", vas a pasar un mal rato.
BigPipe: para los que mezclan cacheado y personalizado
BigPipe (en el core desde Drupal 8.1) resuelve el problema de las páginas que mezclan contenido cacheado y fragmentos por usuario. Sin BigPipe, esa página espera a que se calcule lo dinámico antes de mandar nada. Con BigPipe, mandas primero el esqueleto HTML cacheado y luego inyectas los fragmentos por streaming, con placeholders que se rellenan según PHP los va generando.
El usuario percibe carga casi instantánea porque el navegador ya renderiza lo estático mientras espera lo demás. La trampa: si un bloque personalizado no declara su cache context correctamente (típicamente user o session), Drupal no lo trata como dinámico y acabas sirviendo datos de otro usuario. Esto no es un bug raro: es lo que pasa cuando copias bloques custom sin entender qué contextos declaran.
CDN: acerca los activos al usuario, no más
Una CDN como Cloudflare, Fastly o AWS CloudFront guarda copias de tus activos estáticos —CSS, JS, imágenes, fuentes— en servidores repartidos por geografía. Reduces latencia para usuarios lejos del origen y descargas tu backend.
El módulo CDN de Drupal reescribe las URLs de los activos para que apunten al dominio de la CDN. Lo instalas, defines el dominio base y eliges qué tipos de archivo se sirven desde allí. Cinco minutos de configuración.
Para audiencia internacional o un único origen, la mejora en LCP es brutal: entre el 30% y el 60% menos de latencia en estáticos. Pero si tu tráfico es todo nacional y tu servidor está bien situado, el ROI de una CDN cae. No la metas por moda, métela cuando los números justifiquen el coste operativo.
El stack del servidor también cuenta
Drupal y sus módulos son una parte. La otra es lo que hay debajo.
PHP OPcache
OPcache guarda el bytecode compilado de PHP en memoria, así no se recompila en cada petición. Si tu OPcache no está activo, todo lo demás importa poco. Para Drupal grande, opcache.memory_consumption de 256 MB funciona bien. En producción pones opcache.validate_timestamps=0 para que PHP no compruebe el disco en cada petición, pero entonces tienes que reiniciar PHP-FPM en cada despliegue. Si no quieres ese paso en tu pipeline, déjalo en 1 y asume el coste.
Base de datos
MySQL o MariaDB tienen que aprovechar la RAM disponible. innodb_buffer_pool_size al 70-80% de la memoria en un servidor dedicado a base de datos es la regla de oro. El query cache de MySQL no, ya no: se eliminó en MySQL 8.0 y en versiones previas hacía más daño que bien bajo concurrencia. Revisa los índices de node_field_data, path_alias y las tablas cache_*; un índice mal creado en path_alias te puede colgar el sitio entero cuando alguien hace una purga grande.
HTTP/2 y compresión
HTTP/2 multiplexa peticiones y reduce tiempos cuando el navegador pide varios recursos a la vez. Combinado con Brotli (más eficiente que Gzip), las respuestas HTML, CSS y JS bajan de tamaño. Si todavía sirves HTTP/1.1 sin compresión, deja de leer y arréglalo antes de seguir.
Mide o no toques nada
Optimizar sin medir es trabajar a ciegas. Antes y después de cada cambio, lanza pruebas de carga con Apache JMeter, k6 o Locust simulando patrones reales. No vale "le he dado a F5 varias veces y va rápido".
Para monitorización continua, New Relic o Blackfire.io te dan perfiles detallados de las peticiones PHP, con cuellos de botella por módulo, queries lentas y funciones que se comen la memoria. Blackfire en Drupal va especialmente fino porque puedes perfilar peticiones en producción con overhead mínimo. Si nunca has perfilado tu sitio en producción, probablemente estás optimizando lo que no toca.
Las métricas que tienes que vigilar:
- TTFB medio y percentil 95.
- Ratio de acierto de caché en Varnish (objetivo: >95% para tráfico anónimo, si estás por debajo tienes un problema de cookies o de TTL).
- Uso de memoria en Redis.
- Tiempo medio de respuesta de la base de datos.
- Errores 5xx bajo carga.
El rendimiento se construye en capas, no se compra
Optimizar Drupal de alto tráfico no es un proyecto con fecha de fin. Es un proceso continuo donde código, configuración e infraestructura tienen que cuadrar. Internal Page Cache, Dynamic Page Cache, Varnish con invalidación por tags, Redis para objetos, BigPipe para personalizado, CDN para estáticos y un stack afinado: cada capa cubre lo que la anterior no puede.
Implementa por orden, mide cada cambio y ajusta según el tráfico real. Un sitio con 10.000 páginas al día no necesita la misma arquitectura que uno con 100.000, pero si los fundamentos están bien puestos, escalar es solo añadir capacidad. Si los fundamentos están mal, ningún servidor más grande te va a salvar.
Si necesitas que alguien audite tu sitio Drupal sin venderte humo y te diga qué tocar primero, habla con nuestro equipo de Drupal.