Módulo 8 · Rendimiento y caché

Lección 38 — Estrategias de caché

Qué cachear, cómo invalidar y qué hacer con los datos obsoletos.

Publicada
En esta lección
  1. Ejercicio 1 — El filtro
  2. Ejercicio 2 — Cache-aside con single-flight
  3. Ejercicio 3 — HTTP caching
  4. Ejercicio 4 — La invalidación por eventos
  5. Ejercicio 5 — La medición final
  6. Entrega

El caché que sí vale, medido. Sin solutions.md hasta entregar.

Ejercicio 1 — El filtro

  1. Aplica las dos preguntas a 8 datos de TicketFlow: listado publicado, detalle de evento, disponibilidad de asientos, tarifas de la org, config de features (27), perfil de usuario, carrito/reserva activa, resultados de búsqueda. Tabla: dato | puede estar viejo | invalidable | decisión.
  2. El finding del caché prohibido: busca en tu código cache.get/set sobre datos que fallan el filtro (¿disponibilidad cacheada que decide la compra? ¿permisos cacheados sin TTL?). Documenta cada violación con el riesgo concreto.
  3. El test del FLUSHALL: en staging, borra TODO el Redis y verifica que el sistema se degrada lento pero correcto (el caché es reconstruible). Si algo se rompe: es un write-behind disfrazado — localízalo.

Ejercicio 2 — Cache-aside con single-flight

  1. Implementa listado_protected (la versión del §4 con lock NX + stale) para el listado por ciudad con :v3 y TTL con jitter.
  2. El test del stampede: 50 threads sobre la clave fría con CaptureQueriesContext — esperado ≤3 queries. Corre el test SIN protección primero (50 queries): pega la evidencia del desastre.
  3. La versión del shape: cambia el dict del listado (añade currency), sube a :v4 y verifica que las claves v3 viejas no rompen (expiran o se ignoran). ¿Cuánto tarda la migración de clave en el tráfico real? (respuesta: cero deploys de invalidación).

Ejercicio 3 — HTTP caching

  1. Añade Cache-Control: public, max-age=60 + ETag al listado y al detalle públicos. Test: 2 GET iguales → el segundo es 304 con body vacío (el cliente de test debe enviar If-None-Match).
  2. La decisión por endpoint: ¿qué endpoints JAMÁS llevan Cache-Control public (reservas, perfil, pagos) y qué cabecera llevan en su lugar (private, no-store)? Test que lo verifica en los 3.
  3. El CDN imaginado: escribe el ADR (48) de 8 líneas "CDN en el listado de TicketFlow": qué gana (hit rate estimado con tu métrica de 36), qué invalidación exige, y por qué el ETag con max_updated_at lo hace seguro sin purga.

Ejercicio 4 — La invalidación por eventos

  1. Conecta los consumidores del stream (30): EventUpdated → invalida events:{ciudad}:vN y el detalle; EventPublished → invalida el listado de la ciudad. Test: publica evento → cache.set con dato viejo → consume el evento → cache.get devuelve el nuevo.
  2. La red de seguridad: corrompe a propósito la invalidación (el consumidor falla). El TTL salva el dato: ¿en cuántos segundos máx converge el sistema? Escribe la garantía en una línea (la 32: convergencia definida).
  3. La PII en caché (23): el perfil de usuario cacheado con TTL 60 s y purge por usuario (cache.delete_pattern) en el export RGPD. Test: pide export → el perfil cacheado se purga → el siguiente GET reconstruye con los datos borrados.

Ejercicio 5 — La medición final

  1. Antes/después con k6 (36): la meseta de browse SIN caché vs CON caché — p95, hits/misses, y la BD (pg_stat_statements): ¿cuánto bajó el total_time de las queries de listado?
  2. El costo del caché: ¿cuánto MB de Redis ocupan tus claves? ¿Cuál es la política de evicción (maxmemory-policy allkeys-lru?) y qué pasa con el lock/stale si Redis evicta la clave lock:? Documenta la decisión.
  3. La lista de NO-cacheables con su razón (una línea por dato) en docs/caching.md: el documento que evita el "cachéalo todo" del próximo sprint.

Entrega

Pega la tabla del filtro, la evidencia del stampede sin/protección, el test del 304 y el before/after de k6. Después: Lección 39 — Pools, paginación y compresión.