martes, 11 de abril de 2017

Cacheando contenido para hacer más rápido tu Sitio Web (Varnish)

Hola de nuevo, ahoa escribo para contarles algunos conceptos sobre la cache, para luego pasar a instalar y configurar Varnish, una herramienta especializada en el manejo de la cache. Así que vamos adelante, primero veamos algunos conceptos esenciales para el manejo de la cache.

Características del protocolo HTTP

Un recurso es el objetivo de un request con el protocolo HTTP, cada recurso es identificado por un URI (Uniform Resource Identifier).

En cuanto al manejo de la cache, las propiedades importantes del protocolo http son:
- stateless: que significa que cada request se puede entender por si solo, y que ademas no puede realizar ninguna transformación en otros requests que se estan realizando. Es como si cada request se considerara único.
- Idealmente, la conexión con el servidor es persistente (excepto con HTTP/1.0), para ello existe un header extra llamado Keep-Alive. Este header persiste la conexión entre el cliente y el servidor, lo que implica que no se esta iniciando de nuevo la conexión en cada solicitud, sino que se esta reusando una anterior, esto porque las conexiones son costosas.

- safe: un request se considera seguro si no produce ningún cambio en el servidor. Estos métodos son por ejemplo GET, HEAD, TRACE, OPTIONS, etc.

- idempotent: un request es idempotente si al repetirse con los mismo parametros el resultado es siempre el mismo, por ejemplo PUT, HEAD, DELETE (además, si es safe es idempotente). En pocas palabras, si se realiza el mismo request una y otra vez con los mismos parámetros el estado del servidor es el mismo, aunque la respuesta devuelta podria ser distinta. Por ejemplo, en una Api REST, si hacemos DELETE a un recurso la primera vez obtenemos un código 200 indicando que la eliminación fue exitosa, sin embargo la siguiente que lanzamos el request obtenemos un 404 porque el recurso no existe ya; si bien las respuestas fueron distintas, el estado del servidor es el mismo, la eliminación de un recurso.

Tomando en cuenta estas características, son canditatos para realizar la cache los métodos GET y HEAD. Adicionalmente se puede habilitar la cache para los demás métodos pero esto debe considerarse muy bien pues pueden obtenerse resultados no esperados.

¿Cómo controlo la cache?

La cache es controlada a traves de headers en la respuesta del backend (también se puede realizar manualmente a través de la consola administrativa). Los headers más importantes para el cache son:
- Age: este header es agregado por Varnish para indicarnos el tiempo que ha transcurrido desde que se realizo el pedido del recurso al backend. Sirve para calcular si un objeto en la cache esta fresco o debe ser actualizado comunicandose al backend nuevamente.

- Cache-Control: este encabezado especifica directivas que deben ser cumplidas por las caches. Puede tener varios atributos como valor, pero los más comunes son public que indica que cualquier cache puede almacenar este valor y max-age que indica el periodo en segundos durante el cuál la cache se considera fresca.
Ejemplo, puede ser almacenado por cualquier cache y su tiempo de validez es 20 segundos:
Cache-Control: public, max-age: 20

Este header es muy útil cuando utilizamos varnish para darle un tiempo de vida a los recursos almacenados en cache, sin embargo solamente es útil enlas respuestas del backend ya que Varnish no toma en cuenta este header en las solicitudes del cliente.

- Etag: Se utiliza para diferenciar los estados de la representación de un recurso. Básicamente es para ver cuando un recurso es diferente de otro.

- Expires: También se utiliza para definir el tiempo que un objeto en cache es fresco, sin embargo, para Varnish se utiliza primero siempre el header Cache-Control y luego, solo en caso de no encontrarse definido Cache-Control, se utliza Expires.

¿Com puedo diferencia un request y un response?

La primera línea de un request se denomina request-line, mientras que la primera línea de una respuesta se denomina status-line.

El request-line comienza con un método (GET, POST, ...) seguido por el recurso solicitado y la versión del protocolo (GET /ruta HTTP/1.1).

El status-line comienza con el protocolo seguido por el código y una frase del código (HTTP/1.1 200 Ok)

Ejemplo de Request:
GET         /resources/images    HTTP/1.1
      X-Real-IP: 192.118.21.65
      X-Forwarded-For: 192.118.21.65
      X-Forwarded-Proto: http
      Host: 192.118.2.5:80
      Connection: keep-alive
      Cache-Control:  public, max-age=60
      User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36
      Accept: */*
      Accept-Encoding: gzip, deflate, sdch
      Accept-Language: es-419,es;q=0.8,en;q=0.6
      Cache-Control:  public, max-age=60

Ejemplo de Response
HTTP/1.1     200     OK
     Content-Type: application/json; charset=utf-8
     Content-Length: 289
     Status: 200 OK
     X-Powered-By: Express
     Access-Control-Allow-Headers: Content-type,Accept,X-Access-Token,X-Key
     Access-Control-Allow-Origin: *
     Access-Control-Allow-Methods: GET,PUT,POST,DELETE,OPTIONS
     Cache-Control: public, max-age=30
     Date: Tue, 11 Apr 2017 21:51:48 GMT
     ETag: W/"121-d539f4ff"
     X-Powered-By: Phusion Passenger 5.1.2
     Server: nginx/1.10.2 + Phusion Passenger 5.1.2
     X-Varnish: 98316
     Age: 0
     Via: 1.1 varnish (Varnish/5.1)


¿Cuándo se considera fresco un recurso en la cache?

Un recurso se considera frescon en la cache mientras su tiempo de vida (ttl) sea mayor a su edad (age). Por ejemplo, si almacenamos un objeto en la cache con el header Cache-Control: public, max-age: 20, entonces el objeto se considera fresco por 20 segundos desde el momento en que se obtiene el registro del servidor backend. Un ejemplo bastante simplista de como funciona la cache:

  1. Al primer request (segundo 0), se verifica la cache, como no se encuentra se solicita el recurso al backend y este almacena en cache (validez de 20 segundos establecida por el header Cache-Control) para devolverse luego al cliente.
  2. El siguiente request (segundo 10), verifica en la cache, dado que el registro ya se encuentra cacheado (HIT) simplemente se devuelve al cliente sin consultar al backend nuevamente, además se puede observar un header Age con el valor 10 que indica que el recurso esta en cache por diez segundos.
  3. El siguiente request llega al segundo 31, se verifica la cache y el registro existe, sin embargo no es fresco, por lo cuál se vuelve a solicitar el recurso al backend, se almacena la respuesta en la cache y de devuelve la misma al cliente.

¿Cómo se calcula el tiempo de vida de un recurso en Varnish?

Para calcular el tiempo de vida en cache de un recurso utilizamos el parámetro ttl (time to live), que se inicializa con el primer valor que encuentra de los siguientes:
- s-maxage en el campo Cache-Control de los encabezados de la respuesta
- max-age en campo Cache-Control de los encabezados de la respuesta
- el valor del encabezado Expires en la respuesta
- el valor por defecto del ttl (se configura al iniciar el servicio de varnish con -t, por defecto es 120 segundos)

Adicionalmente, se puede dar un tiempo de gracia a los recursos, esto implica que, aunque su ttl haya expirado, todavía se mantiene el recurso en la cache por un tiempo determinado. Su valor por defecto es diez segundos. De esta manera, incluso si el servidor backend estuviera fuera de línea, varnish esta permitido de devolver el recurso que se encuentra en la cache por un tiempo "extra".

En caso de que el ttl haya expirado y se tenga configurado el tiempo de gracia, el servidor puede devolver un recurso que no este fresco y realizar una llamada al backend para refrescarlo luego asincronamente. Este concepto se explicará en futuros posts pero es necesario tomarlo en cuenta al momento de aprender los conceptos necesarios para entender la cache.

Estados de los recursos en la cache (https://varnish-cache.org)
Y eso es todo por ahora, keep caching :D