MAISON CODE .
/ Tech · Backend · Redis · Caching · Performance · Scale

Almacenamiento en caché de Redis: la arquitectura de los milisegundos

Por qué su base de datos es el cuello de botella. Una guía técnica detallada sobre los patrones de Redis (caché aparte, escritura directa), invalidación de etiquetas e HyperLogLog.

AB
Alex B.
Almacenamiento en caché de Redis: la arquitectura de los milisegundos

En la jerarquía de latencia, la red es lenta, el disco es lento, pero la RAM es instantánea.

  • Consulta PostgreSQL (SSD): 10 ms - 100 ms.
  • Consulta Redis (RAM): 0,2 ms.

Esta diferencia de velocidad de 500 veces es la única razón por la que las aplicaciones web a gran escala permanecen en línea. Si Amazon accediera a su base de datos SQL para cada vista de producto en Prime Day, todo Internet colapsaría.

En Maison Code Paris, tratamos a Redis no solo como un “caché”, sino como un servidor de estructura de datos primario. Es la capa táctica que protege la capa estratégica (la Base de Datos).

Por qué Maison Code habla de esto

En Maison Code Paris, actuamos como la conciencia arquitectónica de nuestros clientes. A menudo heredamos stacks “modernos” construidos sin una comprensión fundamental de la escala.

Discutimos este tema porque representa un punto de inflexión crítico en la madurez de la ingeniería. Implementarlo correctamente diferencia un MVP frágil de una plataforma resistente de nivel empresarial.

Los patrones: cómo almacenar en caché correctamente

El almacenamiento en caché es fácil. Invalidar el caché es imposible. Hay dos patrones de implementación principales que utilizamos.

1. Caché aparte (carga diferida)

Este es el valor predeterminado. La aplicación es responsable de la lectura/escritura.

// db/repo.ts
importar {redis} desde './redis';

función asíncrona getProduct(id: cadena) {
  clave constante = `producto:${id}`;
  
  // 1. Verificar caché
  const en caché = espera redis.get(clave);
  si (en caché) devuelve JSON.parse (en caché);
  
  // 2. Fallo de caché -> Comprobar base de datos
  datos constantes = esperar db.product.findUnique({ id });
  
  // 3. Llenar caché
  si (datos) {
     // Caché durante 60 segundos (TTL)
     espere redis.set (clave, JSON.stringify (datos), 'EX', 60);
  }
  
  datos de devolución;
}
  • Ventajas: Resistente. Si Redis muere, la aplicación funciona (lentamente).
  • Contras: Ventana de datos obsoletos. El usuario puede ver el precio anterior de 60 años.

2. Escritura simultánea (invalidación impulsada por eventos)

Preferimos esto para datos de alta coherencia (como el inventario). Escuchamos los cambios de la base de datos (CDC o eventos de aplicaciones) y bombardeamos el caché.

// servicios/producto.ts
función asíncrona updatePrice(id: cadena, precio: número) {
  // 1. Actualizar DB (Fuente de la Verdad)
  await db.product.update({ donde: { id }, datos: { precio } });
  
  // 2. Invalidar caché inmediatamente
  espere redis.del(`producto:${id}`);
}

El problema más difícil: invalidación por relación (etiquetas)

Imagina que almacenas en caché:

  1. producto:123
  2. categoría:zapatos (Contiene el producto 123)

Si actualiza el Producto 123, elimina producto:123. ¡Pero “categoría: zapatos” todavía contiene la versión anterior del producto! También debes saber qué categorías contienen la clave 123.

Implementamos Invalidación basada en etiquetas usando Redis Sets.

  1. Al crear categoría:zapatos, realizamos un seguimiento de las dependencias: Etiquetas SADD:producto:123 categoría:zapatos
  2. Al actualizar el Producto 123:
    • Encuentra todas las claves que dependen de él: Etiquetas PYME:producto:123 -> Devuelve ['categoría:zapatos'].
    • Eliminarlos: DEL categoría:zapatos.
    • Limpiar el conjunto de etiquetas.

Esta lógica es compleja pero resuelve el problema “¿Por qué la página de inicio sigue mostrando la imagen anterior?” error para siempre.

Serialización: el costo oculto de la CPU

El almacenamiento de cadenas JSON (JSON.stringify) es estándar pero ineficiente.

  1. Almacenamiento: JSON es detallado. "activo": verdadero ocupa 15 bytes.
  2. CPU: JSON.parse bloquea el bucle de eventos. Para una carga útil de 1 MB (página de inicio grande), el análisis requiere 20 ms de tiempo de CPU.

Solución: Paquete de mensajes (msgpack). Es un formato de serialización binaria. Es más rápido y más pequeño.

importar {empaquetar, descomprimir} desde 'msgpackr';
importar Redis desde 'ioredis';

const redis = nuevo Redis ({
  // ioredis soporta buffers binarios
  prefijoclave: 'v1:',
});

await redis.set('clave', paquete(objeto grande)); // Almacena el búfer
const buffer = esperar redis.getBuffer('clave');
const obj = descomprimir (búfer);

Los puntos de referencia muestran que msgpack es 3 veces más rápido de codificar/decodificar que JSON nativo para estructuras grandes.

Estructuras avanzadas: no solo claves y valores

Redis es un “servidor de estructura de datos”. Deja de usarlo como Map<String, String>.

1. HyperLogLog (recuento aproximado)

Problema: “Cuente los visitantes únicos hoy”. Ingenuo: almacene cada IP en un conjunto. SADD {ip}. Para 100 millones de usuarios, esto requiere 1,5 GB de RAM. Solución Redis: Visitantes PFADD {ip}. HyperLogLog utiliza matemáticas probabilísticas (hashes). Cuenta con 100 millones de elementos únicos con 12 KB de RAM. La confiabilidad es del 99,19%. ¿Necesita recuentos exactos de visitantes? No. Necesitas saber “¿Son 10k o 100k?”. HLL es perfecto.

2. Índices geoespaciales

Problema: “Buscar tiendas cerca de mí”. Ingenuo: fórmula SQL Haversine (lenta). Solución Redis: GEOADD almacena 2.3522 48.8566 "Paris". GEORADIUS almacena 2,35 48,85 10 km devuelve resultados en microsegundos utilizando la clasificación Geohash.

3. Limitación de tasas (el grupo de tokens)

Protegemos nuestras API de DDoS mediante contadores de Redis. Usamos un script Lua (para garantizar la atomicidad) que implementa el algoritmo de “Ventana deslizante”. Tecla INCR. Tecla EXPIRAR 60. Si > 100, Rechazar.

El atronador problema del rebaño

Si una funcionalidad de caché es vital, su falla es catastrófica. Escenario:

  1. La clave home_page caduca a las 12:00:00.
  2. A las 12:00:01, 5.000 usuarios solicitan la página de inicio.
  3. 5000 solicitudes obtienen un “error de caché”.
  4. 5000 solicitudes llegaron a la base de datos de Postgres simultáneamente.
  5. El búfer de salida de la base de datos se llena. La base de datos falla.
  6. El sitio web se cae.

Solución: Bloqueo de caché (The Mutex). Cuando ocurre un “Miss”, el código intenta adquirir un bloqueo (SETNX lock:home_page).

  • Ganador: Genera la página. Escribe en caché. Libera el bloqueo.
  • Perdedores: Espere 100 ms. Verifique la caché nuevamente. Resultado: solo 1 consulta llega a la base de datos.

10. Redis Cluster frente a Sentinel (alta disponibilidad)

Single Node Redis es un único punto de falla. Centinela: Maestro de monitores. Conmuta por error a la réplica. Clúster: fragmenta los datos en varios nodos. Para el comercio electrónico, rara vez necesitamos Cluster (la fragmentación es compleja). Una instancia de Redis puede manejar 100.000 operaciones por segundo. Preferimos Sentinel. Pero tenga cuidado: la configuración del cliente para Sentinel es complicada. redis = nuevo Redis ({ centinelas: [{ host: 'sentinel-1', puerto: 26379 }], nombre: 'mymaster' }). Si codifica la IP maestra, la conmutación por error no funcionará.

11. Lua Scripting: la atomicidad es el rey

Quiere “Consultar saldo, Deducir dinero”. Hacer esto en Node.js (Obtener -> Lógica -> Establecer) es una condición de carrera. Escribimos Lua Scripts que se ejecutan dentro de Redis. Los scripts de Lua son atómicos. No se ejecuta ningún otro comando mientras se ejecuta el script. Esto garantiza que la “Deducción de inventario” sea perfectamente consistente, incluso con 500 compradores simultáneos.

13. Estrategias inteligentes de calentamiento de caché

Esperar a que el primer usuario acierte un “Miss” y pague la penalización de latencia es una mala experiencia de usuario. Implementamos Calentamiento activo de caché.

  1. Al implementar: activa un script warm-cache.ts.
  2. Lógica: recupera las 500 URL principales de Google Analytics (a través de API).
  3. Acción: accede a la API interna localhost:3000/api/render?url={top_url}. Esto obliga al servidor a generar la página y completar Redis antes de que llegue el tráfico. El resultado es una latencia de 0 ms para el 80 % de los usuarios inmediatamente después de una implementación.

14. Más allá del valor clave: pila de Redis (búsqueda y JSON)

El nuevo Redis Stack le permite ejecutar consultas similares a SQL en documentos JSON. FT.SEARCH productIdx "@title:Zapatos @price:[0 100]" esto se ejecuta en la memoria. Es 50 veces más rápido que Elasticsearch para conjuntos de datos pequeños (<1 millón de elementos). Usamos esto para funciones de “Búsqueda instantánea” donde Algolia es demasiado cara o demasiado lenta.

15. Conclusión

Redis es la herramienta más poderosa en el cinturón de herramientas del ingeniero backend. Sirve de puente entre el mundo rígido y lento de las bases de datos ACID y el volumen de solicitudes rápido y caótico de la web moderna.

Pero requiere disciplina. Un caché salvaje (sin TTL, sin política de desalojo y con claves enormes) es una pérdida de memoria que espera colapsar su servidor.


**[Contrate a nuestros arquitectos](/contact)**.