Cómo implementar un Reverse Proxy en Symfony

Por muy eficiente que sean nuestras aplicaciones más pronto que tarde descubrimos que nos damos contra un muro en el rendimiento. ¿Has probado a hacer alguna prueba de carga sobre tu web? Probablemente te haya decepcionado los pobres resultados alcanzados. Por suerte o por desgracia es un problema bastante común y existen numerosas posibles soluciones. Debemos establecer una estrategia de caché y aunque existen muchos puntos donde aplicarla (la base de datos, compilación del código fuente, …) la que se ha convertido en un imprescindible para mí es el uso de un Reverse Proxy. Y si además trabajas con Symfony su implantación será mucho más sencilla.

Pero antes, si no estás convencido de que necesitas implementar una estrategia de caché en tu web o no sabes como va a beneficiarte puedes leer el siguiente artículo.

Qué es un Reverse Proxy y como funciona

Un Reverse Proxy (también llamado HTTP Gateway Cache) es un sistema (software, hardware o ambos) que se sitúa entre nuestros visitantes y nuestra web y que almacena el resultado de las peticiones a modo de caché. De ese modo cuando una petición guardada se repite puede servir el resultado mucho más rápido y sin sobrecargar la web.

Piensa en la caché de tu navegador, ¿no es increíblemente más rápido recargar una página almacenada que visitarla por primera vez? Pero eso solo beneficia a un usuario que vuelve a entrar en una misma página. ¿No sería genial que pudiéramos tener una caché compartida entre todos los usuarios para que podamos aprovecharla mucho más? Y eso es precisamente lo que hace un Reverse Proxy. Su funcionamiento se basa en el estándar de caché de HTTP lo que lo hace sencillo pero muy potente.

Mediante el uso de unas cabeceras en nuestras respuestas le indicamos a la caché si ese contenido es almacenable, para cuanto tiempo puede almacenarse o cuando fue la última vez que se modificó. Cuando se registra una nueva petición se comprueba si existe coincidencia con lo que hemos almacenado previamente y si esos datos aún son válidos. Si todo es correcto se devuelve la respuesta y nuestra web no llega a enterarse. Si la información no es válida o no hay nada almacenado se pasa la petición al sistema backend o web que genera una respuesta y la devuelve al proxy. Tras analizar las cabeceras, para saber si esa respuesta debe almacenarse, le devuelve la respuesta al origen de la petición.

Cómo implementar un Reverse Proxy en nuestra web Symfony

Aunque existen varios sistemas proxy que podemos utilizar (como Varnish o incluso nginx) usando Symfony lo más sencillo es usar el Reverse Proxy que viene integrado Symfony Reverse Proxy. Está escrito en PHP y no es ni de lejos el más eficiente pero es un gran punto de partida ya que nos permite configurar y probarlo todo sin complicaciones. Una vez que lo tengamos todo en su sitio cambiarlo a otro como Varnish es mucho más sencillo.

Para Symfony 3

Básicamente cambiando un par de lineas del script de entrada (o front controller) estamos listos para empezar a trabajar.

Más información aquí.

Para Symfony 4

En este caso tenemos que trabajar un poco más. Primero creamos una nueva clase CacheKernel

Y luego modificamos el script de acceso:

Más información aquí.

Como usar nuestro nuevo Reverse Proxy

Una vez tenemos en funcionamiento nuestro Proxy, debemos de comunicarle al proxy que hacer con cada respuesta. Para ello vamos a usar las cabeceras HTTP que devolvemos con  cada respuesta. Vamos a trabajar con las siguientes cabeceras fundamentalmente:

  • Cache-Control. Puede tomar los valores publicprivate y nos permite indicar en el primer caso si la respuesta se puede compartir con otros usuarios o si es solo válida para el usuario que hace esta petición.
  • Expires. Indica la fecha en la que deja de ser válida la información almacenada en la cache.
  • S-MaxAge. Indica la cantidad de segundos en la que la respuesta seguirá siendo válida.
  • ETag. Permite generar una identificación para la petición de modo que sepamos diferenciarlas de las demás. Por norma será la URL pero no es siempre el caso.
  • Last-Modified. Indica cuando fue la última vez que se generó el contenido. Puede ser útil para verificar de forma rápida si el contenido ha sido modificado o no desde la última petición desde el proxy.

Gracias a estas cinco cabeceras vamos a delimitar si se debe cachear la respuesta, hasta cuando va a ser válida y como identificar que está petición es única.

Como controlar las cabeceras HTTP Cache en Symfony

Para ello debemos modificar el objeto Response para incluir o modificar las cabeceras concretas. Una vez tenemos generado una respuesta (por ejemplo al renderizar una vista o creándola directamente) podemos invocar al método setCache para establecer todos los valores de golpe o uno a uno a los métodos correspondientes:

Otra opción es usar las anotaciones @Cache antes del controlador para establecer valores a todos los métodos o delante de un método concreto para que se aplique solo a las respuestas de dicha acción.

Relación entre las cabeceras S-MaxAge y Cache-Control

Una peculiaridad a tener en cuenta es que si establecemos S-MaxAge a un valor mayor a 0 automáticamente se establece el valor de Cache-Control a público.

Como usar Last-Modified para aumentar el rendimiento aún más

Cada vez que los datos almacenados expiran el Reverse Proxy realiza una petición a la web para obtener de nuevo los datos. Sin embargo, esto no quiere decir que los datos hayan sido modificados. Imaginémonos un sistema tipo blog en el que al visitar un artículo tenemos un tiempo de caché pública de 5 minutos (S-MaxAge = 300 segundos). Cuando los datos almacenados en caché tengan más de 300 segundos debemos preguntar a nuestra web si el artículo ha sido modificado. ¿Y si no ha sido modificado? ¿por qué tenemos que volver a generar la respuesta? De hecho no es necesario ya que podemos indicarle al proxy que los datos siguen siendo válidos lo que le darían otros 300 segundos de vida.

Si usamos la fecha de última modificación del artículo como valor para la cabecera Last-Modified, cuando recibamos una nueva petición solo tendremos que comprobar que la fecha almacenada en base de datos sea mayor que el valor de la cabecera Last-Modified. En ese caso el artículo ha sido modificado y no tenemos más remedio que procesar la respuesta de nuevo.

Trabajando con Edge Side Includes

Cuando empieces a trabajar con cache pronto te encontrarás con el problema de que casi todo la página puede cachearse pero hay una pequeña parte que es totalmente privada. Por ejemplo es lo que ocurre con todas las webs en las que podemos loguearnos y muestran nuestro usuario en la cabecera. ¿Vamos a perder la posibilidad de mejorar enormemente el rendimiento de la página por ese pequeño detalle? En estos casos podemos usar una técnica llamada ESI o Edge Side Includes. Se trata de unas etiquetas que incluimos en el html que le indican al servidor proxy que debe hacer una petición individual para sustituir dicha etiqueta por el contenido real.

Esto nos permite cachear trozos de la web de forma totalmente independiente con diferentes estrategias. En el ejemplo anterior del blog podemos cachear el artículo de forma totalmente diferente a los comentarios del mismo. Los comentarios deben ser mucho más dinámicos y un tiempo de 300 segundos seguramente sea excesivo.

De nuevo Symfony nos facilita mucho la labor. Primero debemos activar el uso de esi en nuestra web.

Y posteriormente usamos en nuestras plantillas twig:

Sustituir Symfony Reverse Proxy por otro servidor proxy

El último paso una vez tengamos todo en su lugar es sustituir el proxy de Symfony por uno más eficiente. Los pasos dependerán de la versión de Symfony y del proxy escogido pero recuerda que debes deshacer los cambios realizados al script de entrada o estarás realizando un doble proxy: Symfony Reverse Proxy y el nuevo proxy.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *