El sistema de caché de RedwoodJS se integra directamente en la capa de servicios para optimizar la velocidad de respuesta y minimizar las consultas redundantes a la base de datos. Al emplear una arquitectura basada en el algoritmo LRU (Least Recently Used), este gestor libera memoria automáticamente eliminando los elementos menos utilizados, permitiendo que las aplicaciones escalen sin comprometer los recursos del servidor.
Arquitectura y Funcionamiento
RedwoodJS permite abstraer la complejidad de la gestión de memoria mediante un adaptador que soporta motores externos como Redis y Memcached. El flujo de trabajo redirige las peticiones GraphQL hacia la capa de caché antes de tocar la base de datos: si el dato existe (cache hit), se retorna inmediatamente; de lo contrario (cache miss), se ejecuta la lógica de negocio, se almacena el resultado y se entrega al cliente.
Configuración del Entorno de Caché
Para habilitar el soporte de caché, es necesario integrar el cliente correspondiente en el lado de la API. Redwood proporciona generadores automáticos para facilitar este proceso.
# Configuración para Redis
yarn redwood setup cache redis
# Alternativa para Memcached
yarn redwood setup cache memcached
Una vez ejecutado el comando, se genera el archivo api/src/lib/cache.js. Aquí se puede personalizar la instancia del cliente y los parámetros globales:
import { createCache, RedisClient } from '@redwoodjs/api/cache'
import { logger } from './logger'
const redisClient = new RedisClient('redis://127.0.0.1:6379')
export const { cache, cacheFindMany } = createCache(redisClient, {
logger,
timeout: 450,
prefix: 'catalogo_app',
})
Implementación de Estrategias de Almacenamiento
La eficiencia de la caché depende críticamente del diseño de las claves (cache keys) y los tiempos de expiración (TTL).
Definición de Claves Dinámicas
Es recomendable incluir marcas de tiempo o versiones en las claves para forzar la actualización cuando los datos cambian:
- Por usuario:
perfil:${context.currentUser.id} - Por contenido:
articulo:${slug}:${lastUpdate.getTime()}
Uso de la Función cache
La función cache envuelve funciones asíncronas para automatizar el almacenamiento. A continuación, un ejemplo de cómo cachear un catálogo de productos con una expiración de 30 minutos:
export const obtenerProductosActivos = () => {
return cache('productos-destacados', () => {
return db.producto.findMany({
where: { activo: true },
take: 10
})
}, { expires: 1800 })
}
Optimización de Consultas Masivas
Para manejar colecciones de datos extensas, Redwood ofrece cacheFindMany, que simplifica la integración con Prisma:
export const listaUsuariosPremium = () => {
return cacheFindMany('usuarios-vip', db.user, {
conditions: { where: { plan: 'PREMIUM' } },
expires: 3600
})
}
Gestión Manual y Invalidación de Datos
Existen escenarios donde es imperativo eliminar una entrada de caché antes de que expire naturalmente, como cuando se actualiza un registro crítico:
import { deleteCacheKey } from 'src/lib/cache'
export const actualizarInventario = async ({ id, stock }) => {
// Invalidamos la entrada específica antes de actualizar
await deleteCacheKey(`producto-id-${id}`)
return db.producto.update({
where: { id },
data: { stock }
})
}
Validación Mediante Pruebas Unitarias
RedwoodJS facilita herramientas de testing para asegurar que la lógica de caché funcione según lo previsto sin necesidad de un servidor Redis real en el entorno de pruebas:
import { testCacheClient } from '@redwoodjs/testing/cache'
describe('Caché de Servicios', () => {
it('debería almacenar correctamente los datos del catálogo', async () => {
const mockData = { id: 1, nombre: 'Producto Test' }
// Simulación de hit de caché
expect(testCacheClient).toHaveCached('catalogo-1', mockData)
})
})
Configuración de Parámetros Avanzados
El objeto de configuración de createCache permite ajustar el comportamiento del sistema:
| Parámetro | Descripción |
|---|---|
timeout |
Tiempo máximo de espera (ms) antes de abortar la operación de caché y pasar a la DB. |
prefix |
Cadena de texto que se antepone a todas las claves para evitar colisiones. |
fields |
Mapa de nombres para identificar campos de identidad (por defecto 'id' y 'updatedAt'). |
Consideraciones Finales de Rendimiento
Para maximizar los beneficios, se deben seguir estas pautas:
- Granularidad: Cachear objetos pequeños e individuales suele ser más eficiente que grendes blobs de datos.
- Precalentamiento: En aplicaciones con picos de tráfico, es útil realizar consultas de "calentamiento" para llenar la caché antes de que lleguen los usuarios.
- Monitoreo: Vigilar el uso de memoria en Redis para ajustar las políticas de desalojo si la base de datos crece demasiado.