La verdad sobre la cache hit ratio
Puesto que el cacheo es uno de los servicios fundamentales que presta toda CDN (content delivery network), una de las métricas más habituales con que evaluar la performance de la CDN es la cache hit ratio (CHR, por sus siglas en inglés) o proporción de resultados de la caché. Los clientes de CDN llevan empleándola varios años como indicador principal del grado de calidad tanto de los servicios que estas redes prestan a sus usuarios como de la gestión de su tráfico. No resulta extraño llegar rápidamente a la conclusión de que los usuarios están sacando el máximo provecho de las CDN a partir de lecturas que indican una "CHR del 98 %".
Sin embargo, hay muchas cosas ocultas sobre la CHR y la métrica que tanto apreciamos podría no estar indicándonos lo que pensamos que nos indica. Así que me pareció una buena idea investigar qué mide la cache hit ratio en realidad y la posibilidad de que necesitemos nuevas formas de calcularla y evaluarla.
Cálculo convencional de la CHR
Durante muchos años, las CDN han utilizado la fórmula siguiente para calcular la CHR:
En la que requeststotal es el total de solicitudes de cliente recibidas por la CDN y requestsorigin es el número de esas solicitudes que lograron llegar al origen. Fundamentalmente, esto significa que, si enviamos 100 solicitudes a la CDN y solo una sale de esta y llega al origen, tendríamos una CHR del 99 %.
El problema surge cuando nos dicen "CHR del 99 %": de forma instintiva pensamos que eso significa que el 99 % de las solicitudes de nuestros usuarios fueron atendidas en el borde de la CDN desde las cachés más cercanas a los clientes de los que partieron las solicitudes. La verdad es que eso no tiene por qué ser necesariamente así; para entender el motivo, tenemos que hablar del diseño del sistema de las CDN, las jerarquías de las memorias caché y el contenido "long-tail".
De qué forma están diseñadas las CDN
No quiero entrar en mucho detalle, y tampoco se puede hablar de todas las CDN, pero, al más alto nivel, está claro que una CDN es una red distribuida de servidores proxy de cacheo. Estos servidores proxy, y por tanto sus memorias caché, son compartidos. Reciben y almacenan en caché solicitudes en un elevado número de dominios, todos los cuales pertenecen a los clientes de la CDN. Esto quiere decir que también se comparte el almacenamiento de todos y cada uno de los servidores. Como el almacenamiento es finito, siempre hay alguna clase de algoritmo que tiende a incluir modelos de expulsión para la gestión de ese almacenamiento. Mientras que los objetos solicitados con frecuencia suelen tener más probabilidades de permanecer en cachés durante más tiempo (suponiendo que no hayan excedido su vigencia de actualización), los objetos solicitados con menos frecuencia tienen más probabilidades de ser expulsados de la caché incluso si sus encabezados Cache-Control
indican que la duración de su almacenamiento en caché debe ser más larga.
Pero que un objeto aún actualizado sea expulsado de un servidor de caché por la gestión del almacenamiento no implica que vaya a ser expulsado necesariamente de toda la CDN y de todos sus servidores de caché. Las CDN suelen tener implantado algún tipo de modelo jerárquico por el que, si un servidor con el que se esté comunicado el cliente no tiene el objeto en su caché, probablemente pida el objeto a un servidor del mismo nivel o a un servidor primario antes de tratar de capturarlo en el origen. El problema está en que el servidor del mismo nivel o el servidor primario no siempre están junto al servidor al que está conectado el cliente. De modo que el tiempo que se tarda en capturar el objeto y proporcionárselo al cliente podría verse afectado. Con este diagrama se entiende todo mejor:
Cuando una caché de borde recibe la solicitud proveniente del cliente, la respuesta puede proceder de cualquiera de las posibles ubicaciones de almacenamiento —algunas de las cuales ni siquiera están en el servidor—, aunque la conexión TCP se haya establecido entre el cliente y ese equipo concreto. El objeto puede proporcionarse desde la memoria de ese equipo (en el mejor de los casos), el almacenamiento en disco de dicho equipo (esperemos que eso signifique un SSD, porque, de lo contrario, se penaliza el rendimiento de forma adicional), un servidor del mismo nivel o primario, o un servidor del mismo nivel o primario no tan cercano. Y el rendimiento se ve mermado a lo largo de toda esa cadena ya que el objeto se proporciona desde ubicaciones cada vez más distantes con respecto al borde de la red.
La ubicación desde la que el objeto se proporciona en última instancia a menudo guarda relación directa con la frecuencia con la que se captura. Cuanta menos sea la frecuencia con la que se solicita algún elemento, más probabilidades hay de que la solicitud acabe siendo un error en la caché de borde a la que el cliente esté conectado. En otras palabras, a pesar de que el contenido long-tail (que no está sujeto a capturas con mucha frecuencia, como publicaciones en redes sociales, fotos de perfil, extensos inventarios de comercio electrónico, etc.) siga siendo proporcionado desde la CDN, no necesariamente lo es desde una caché próxima al cliente.
Pero, al calcular la cache hit ratio, la mayoría de las redes CDN consideran "resultado" cualquier respuesta proveniente de cualquiera de sus cachés siempre que no hubiera llegado al origen. Es en este aspecto en el que la CHR, como métrica, puede llevar a engaño y darte una impresión falsa del rendimiento que una CDN está prestando a tus clientes. Y es también en este aspecto en el que el modelo de almacenamiento de objetos de cualquier CDN pasa a ser decisivo. Si se implanta sin la suficiente densidad de borde, sin un escalado adecuado en este o sin algoritmos de expulsión óptimos, una parte considerable de tu contenido long-tail posiblemente se proporcione desde las profundidades de la CDN, aunque tu CHR parezca ser alta.
Una forma mejor de calcular la CHR
Lo que nos está diciendo el método convencional de cálculo de la cache hit ratio, o lo que no nos está diciendo, precisa un replanteamiento de la forma en la que debemos calcular esta métrica que tanto nos importa. Para aportar una métrica que sea indicativa del rendimiento, lo que queremos saber en realidad es el porcentaje de objetos que fueron proporcionados desde una caché de CDN ubicada en el mismo borde de la red. Algo más parecido a esto:
Que realmente es lo mismo que esto otro:
Ejemplo en el que hitsedge y missesedge son, respectivamente, el número de resultados de la caché y el número de errores de dicha memoria en el borde de la red. Esta fórmula de la CHR refleja con precisión el porcentaje de solicitudes almacenables en caché que se proporciona desde el borde de la red, en la ubicación más cercana a los usuarios.
A decir verdad, el método convencional sigue siendo extremadamente útil. Se trata de una métrica muy buena para medir la transferencia del servidor, ya que nos indica el porcentaje de solicitudes que se quedan a medio camino del origen. Sin embargo, para evaluar el rendimiento, lo que verdaderamente nos importa es qué ocurre en el borde. Por tanto, la mejor forma de avanzar es tener en cuenta dos métricas diferentes de la CHR: una para la CHR en el borde:
y otra para la CHR global:
La métrica CHRedge corresponde al rendimiento, y la CHRglobal , a la transferencia. Ambas son valiosas y ofrecen nuevas perspectivas, pero cada una cuenta una historia distinta. CHRedge es recomendable para calcular el volumen de tu contenido que se proporciona desde el borde de la red, en una ubicación más cercana a los usuarios. Esto se traduce directamente en ventajas para el rendimiento. CHRglobal , en cambio, suele indicar el volumen de tráfico que se queda en el camino y no llega a tu origen. La consecuencia directa de su uso es la transferencia de procesamiento y de infraestructuras, lo cual a su vez posiblemente se traduzca en cuantiosos ahorros de costes.
Calcular los cache hit ratios con Fastly
La red de Fastly está diseñada de tal modo que todos nuestros puntos de presencia (POP) sean ubicaciones en el borde; se pueden visualizar en el mapa de nuestra red. Con el fin de lograr escalado y densidad de almacenamiento, disponemos de capas de jerarquía de caché dentro de cada uno de los POP, de forma que sean transparentes para ti y para las solicitudes tuyas y de tus usuarios. Esto quiere decir que, incluso si una solicitud es un error de caché en el servidor al que esté conectado el cliente, existen bastantes probabilidades de que esta sea proporcionada desde una caché ubicada en el interior del POP con el que se esté comunicando, convirtiéndolo así en un resultado de caché en el borde. También puedes implantar un shielding para lograr una capacidad adicional de cacheo a fin de incrementar tu CHR global y reducir el tráfico dirigido a tu origen.
La CHR que se te notifica a través del panel de control o de la API de estadísticas se calcula de la siguiente manera:
Con servicios cuyos orígenes no estén blindados, esto equivale de hecho tanto a CHRedge como a CHRglobal ya que todo resultado es un resultado en el borde y todo error es un error en el borde (y también una solicitud enviada al origen).
Si tu servicio posee un origen blindado, en el que el POP con blindaje agregue otro nivel de cacheo, la fórmula anterior sigue siendo válida, pero el recuento de resultados/errores suele incluir resultados y errores que fueron blindados. En otras palabras, algunas solicitudes tienden a ser objeto de recuento dos veces. De modo que se trata de una métrica híbrida. Aunque existe un par de métodos con los que desvincular y calcular CHRedge y CHRglobal de forma independiente respecto de cualquier servicio blindado, a menudo prefiero hacerlo mediante nuestro envío de registros en tiempo real.
The following VCL snippet sets up log streaming for CHR calculation with a couple of variables locales:
sub vcl_log {
#FASTLY log
declare local var.cache_status STRING;
declare local var.request_type STRING;
set var.cache_status = if(fastly_info.state ~ "HIT($|-)", "HIT", if(fastly_info.state ~ "PASS($|-)", "PASS", "MISS"));
set var.request_type = if(req.http.fastly-ff, "shield", "edge");
log {"syslog req.service_id logging_endpoint :: "} var.cache_status "," var.request_type "," server.datacenter;
}
En este fragmento, la variable local cache_status
a menudo presenta uno de entre tres posibles valores: HIT
, MISS
o PASS
. Nos interesa excluir ocurrencias de PASS
en nuestro cálculo, ya que, ante todo, no son objetos susceptibles de cacheo. La variable local request_type
suele indicarnos si esta solicitud fue proporcionada desde un POP con blindaje o un POP de borde (o desde un POP con blindaje que se comunique directamente con un cliente en vez de con otro POP de Fastly, lo cual lo convierte en términos funcionales en un POP de borde respecto de esa solicitud). Entonces registramos cache_status
, request_type
y server_datacenter
, que es el código del POP, compuesto por tres letras.
A partir de este código VCL, nuestras entradas del registro tendrán este aspecto:
Jan 7 23:15:26 cache-sjc3131 logging_endpoint[218999]: HIT,edge,SJC
Jan 7 23:15:33 cache-sjc3638 logging_endpoint[348011]: HIT,edge,SJC
Jan 7 23:15:40 cache-iad2120 logging_endpoint[342933]: MISS,shield,IAD
Jan 7 23:15:40 cache-sjc3644 logging_endpoint[341583]: MISS,edge,SJC
Jan 7 23:16:07 cache-iad2127 logging_endpoint[342933]: HIT,edge,IAD
Jan 7 23:16:24 cache-iad2127 logging_endpoint[14817]: MISS,edge,IAD
Jan 7 23:16:40 cache-iad2120 logging_endpoint[218999]: HIT,shield,IAD
Jan 7 23:16:40 cache-sjc3624 logging_endpoint[438579]: MISS,edge,SJC
Jan 7 23:23:25 cache-iad2122 logging_endpoint[342933]: PASS,shield,IAD
Jan 7 23:23:25 cache-sjc3140 logging_endpoint[348011]: PASS,edge,SJC
Así, el cálculo de CHRedge y CHRglobal a partir de estas entradas del registro es muy sencillo:
En el que:
miss
edgees una entrada del registro concache_status = “MISS”
yrequest_type = “edge”
;miss
edgees una entrada del registro concache_status = “MISS”
yrequest_type = “edge”
.
Del mismo modo:
En el que:
miss
shieldPOPes una entrada del registro concache_status = ”MISS”
yserver.datacenter = <your_shield_pop>
;miss
edgees una entrada del registro concache_status = “MISS”
yrequest_type = “edge”
;miss
edgees una entrada del registro concache_status = “MISS”
yrequest_type = “edge”
.
Ayudándonos a modo de ejemplo de las entradas del registro de muestra anteriores —cuyo origen blindado está en nuestro POP (IAD) de Dulles (Virginia)—, calcularíamos lo siguiente:
Y:
Lo cual proporciona un ejemplo sencillo pero apto de por qué las dos métricas indican cosas diferentes y deben ser evaluadas por separado.
Aunque el envío de registros facilita la desvinculación de las dos métricas respecto de servicios blindados, trabajamos para simplificar aún más este proceso notificando en un futuro próximo las métricas de manera independiente a través de nuestro sistema estadístico.
Qué depara el futuro
En Fastly hablamos mucho de llevar a cabo nuestras operaciones en el borde y de los motivos por los que eso es positivo para tus usuarios finales. El cacheo sigue siendo una de estas funciones principales; de hecho, no debes ignorar la calidad del cacheo que la CDN realiza de tu contenido en el borde. Llegados a este punto, es importante entender de qué forma notifican las CDN la proporción de resultados de caché y qué significa esto para tu contenido y para la experiencia de tus usuarios finales. El cálculo de la cache hit ratio en el borde y en términos globales es una forma magnífica de hacerte de verdad una idea de cómo se proporciona tu contenido.