Phil Karlton dijo que sólo hay dos cosas difíciles en informática: invalidación de caché y nombrar cosas. KV hace que el primero sea aún más difícil, porque no controlas cuándo llega la invalidación a cada punto de presencia.
Eliminar una clave en KV no la elimina inmediatamente del mundo. La eliminación va al almacén central y se propaga a los PoP con la misma dinámica de coherencia eventual que las escrituras: hasta 60 segundos para llegar a todos los puntos de presencia. Durante esta ventana, los PoP que aún no han recibido la eliminación continúan entregando el valor anterior a cualquier solicitud entrante. No sabes qué PoP lo recibieron y cuáles no. No hay forma de forzar la propagación inmediata.
Esto hace que la invalidación de la caché en KV sea un problema de diseño, no un problema de operación. No se resuelve con un botón "invalidar ahora", sino que se resuelve eligiendo un modelo clave que minimice el impacto del retraso de propagación.
Claves versionadas: la solución más robusta
El estándar más sólido para el contenido que debe invalidarse es mantener una dirección indirecta explícita entre el nombre lógico de los datos y la clave física donde se almacenan.
En lugar de escribir el HTML renderizado de una página en homepage-html y luego eliminarlo cuando cambia el contenido, lo escribe en homepage-html-v42. Una segunda clave, homepage-html-version, contiene solo la cadena v42. El trabajador lee primero la clave de versión, construye el nombre de la clave de contenido y recupera el valor.
Cuando el contenido cambia, escribe el nuevo HTML en homepage-html-v43 y actualiza homepage-html-version a v43. No es necesario eliminar inmediatamente la clave anterior; simplemente deja de ser referenciada. Su costo de almacenamiento continúa existiendo hasta que realiza la limpieza, pero la inconsistencia de invalidación ya no es un problema: cualquier PoP que recibió la actualización homepage-html-version ya obtendrá la nueva clave. El contenido antiguo solo se entrega a los PoP que aún no han recibido la actualización de la clave de versión, y esto sucede dentro de la ventana de 60 segundos, independientemente de lo que haga.
El costo de este enfoque es una lectura adicional por solicitud: primero la clave de la versión y luego el contenido. En la mayoría de los casos, este costo es irrelevante en términos de latencia: las dos lecturas son paralelas si el trabajador las realiza con Promise.all, y ambas llegan desde la caché de PoP en menos de milisegundos cuando están calientes.
Coordinar KV con purga de CDN
Para el contenido servido desde la CDN de Cloudflare (no directamente desde el Worker), hay una capa adicional de almacenamiento en caché por encima del KV. Es posible que la CDN haya almacenado en caché una respuesta generada con un valor KV antiguo, e incluso si el KV ya tiene el nuevo valor en todos los PoP, la respuesta almacenada en caché en la CDN se seguirá entregando hasta que caduque.
La solución es coordinar la actualización de KV con una purga de CDN. La API Cache Purge de Cloudflare le permite invalidar URL específicas o etiquetas de caché a través de la API. Escribe el nuevo valor en el KV y, en la misma operación administrativa, llama a la API de purga para las URL afectadas. Desde la perspectiva de los clientes que pasan por la CDN, la nueva respuesta aparece inmediatamente después de la purga, independientemente de cuántos PoP todavía estén propagando el valor KV.
Este patrón requiere que usted controle el proceso de actualización de contenido y tenga acceso a la API de purga. Para los flujos de CMS en los que un editor publica contenido, es una integración razonable: el webhook de publicación actualiza el KV y activa la purga.
Obsoleto mientras se revalida con esperar hasta
El patrón obsoleto mientras se revalida muestra contenido potencialmente obsoleto y al mismo tiempo activa una actualización en segundo plano. En el contexto de Workers, ctx.waitUntil() permite ejecutar trabajo asincrónico después de enviar la respuesta al cliente.
Implementación típica: el trabajador lee el valor KV actual y sirve de inmediato. Al mismo tiempo, activa a través de ctx.waitUntil() una función que comprueba si es necesario actualizar el valor (consultando una fuente, por ejemplo) y escribe el nuevo valor en el KV si es necesario. El cliente recibe la respuesta sin esperar la actualización. Es posible que la siguiente solicitud ya reciba el nuevo valor, dependiendo de cuándo se complete la propagación.
La compensación es explícita: se intercambia latencia cero con el cliente por una ventana en la que se puede ofrecer contenido obsoleto. Para la mayoría de los escenarios de almacenamiento en caché de contenido, esta compensación es aceptable. Para datos en los que la obsolescencia tiene consecuencias operativas (precios, disponibilidad de existencias, permisos de acceso), el estándar no es apropiado.
TTL como reemplazo de la invalidación explícita
Para los casos en los que no es necesaria una invalidación precisa, TTL es el mecanismo más sencillo. KV admite expirationTtl (dentro de segundos) y expiration (marca de tiempo Unix) definidos en el momento de escribir este artículo.
Un indicador de función con un TTL de 60 segundos caduca automáticamente. La siguiente solicitud después del vencimiento buscará el almacén central y devolverá el valor más reciente, o un valor vacío, que el trabajador puede interpretar como "bandera deshabilitada". No es necesario eliminar ni realizar un seguimiento explícito del estado.
Para contenido con una frecuencia de actualización predecible, el TTL alineado con el ciclo de actualización elimina la necesidad de una invalidación activa. Un informe generado cada vez puede tener un TTL de 3600 segundos. El valor más antiguo posible que verá cualquier usuario es aproximadamente una hora, y esto es una elección de diseño, no un defecto de coherencia.
Lo que no funciona: eliminar y reescribir inmediatamente
Un patrón que aparece con frecuencia y no resuelve el problema es eliminar la clave antigua y escribir la nueva en secuencia. Esto no elimina la ventana de inconsistencia: las dos operaciones se propagan de forma independiente a los PoP. Un PoP puede recibir la eliminación pero aún no haber recibido la nueva escritura, y durante esa ventana devolverá "no encontrado" para esa clave. Dependiendo de cómo el trabajador maneje este caso, esto podría resultar en un error o una reserva inesperada.
Eliminar y reescribir tienen el doble de operaciones de propagación con el triple de posibles estados intermedios: antiguo, no encontrado, nuevo. Siempre son preferibles las claves versionadas o TTL.
Cómo la coherencia eventual cambia el diseño
El patrón más saludable para trabajar con KV es aceptar la coherencia final como una característica del sistema, no como una limitación que hay que solucionar. Esto significa diseñar flujos donde sea tolerable una ventana de hasta 60 segundos de inconsistencia y utilizar diferentes herramientas cuando no lo sea.
Para datos que requieren coherencia inmediata en todos los PoP, KV no es la herramienta adecuada. D1 con lecturas dirigidas al primario u Objetos Durables para estado con acceso serializado, cubren casos donde el modelo de consistencia eventual de KV no funciona.
La invalidación elegante en KV no necesita ocurrir con urgencia, porque el diseño del sistema se hizo para tolerar la ventana de propagación. Cualquier intento de forzar una coherencia inmediata actuará en contra de la arquitectura, no a favor de ella.
Lea también
- Cloudflare KV: ¿Qué significa distribuido globalmente cuando necesitas escribir?
- KV para limitación de velocidad, indicadores de funciones y configuración distribuida: dónde funciona y dónde falla
- KV en producción: los patrones que funcionan y los que engañan al principio
- KV vs R2 vs Cache API: cuándo usar cada nivel de almacenamiento de Cloudflare
- Lo que solo hacen los Trabajadores, lo que solo hacen los Pajes y donde se encuentran los dos
- Cloudflare D1: La base de datos SQLite en el borde, y por qué 'borde' no significa lo que parece
