Marcio Cunha

¿API lenta en NestJS? Cómo aplicar caché distribuida con Redis

Aprende a eliminar la latencia en endpoints críticos de NestJS aplicando estrategias avanzadas de caché distribuida e invalidación con Redis.

Marcio Cunha•4 min
También disponible en:PortuguêsEnglish
Resumen
  • El almacenamiento en caché reduce drásticamente el acceso directo a bases de datos en endpoints de alta lectura
  • Redis actúa como una capa rápida de memoria compartida entre múltiples instancias de una aplicación
  • La invalidación basada en eventos previene datos obsoletos y mantiene la consistencia de los registros
  • La serialización eficiente de objetos optimiza el uso de memoria y acelera las búsquedas en el clúster
  • Los interceptores personalizados en NestJS automatizan el ciclo de vida de la caché sin contaminar controladores

Por qué las APIs lentas exigen una estrategia de caché distribuida

Cuando una aplicación web crece, la base de datos suele ser el primer componente en sufrir bajo el peso de consultas repetitivas. Si cientos de usuarios piden la misma información al mismo tiempo, la API comienza a demorar en responder y la experiencia de uso se desploma. En la práctica, esto significa que el servidor pierde tiempo procesando datos que no han cambiado desde la última solicitud. Para resolver este problema de escala, necesitamos una capa intermedia que guarde las respuestas listas y entregue los datos en fracciones de milisegundo.

En arquitecturas modernas basadas en microservicios o en múltiples servidores ejecutándose en paralelo, guardar datos solo en la memoria RAM de una sola máquina deja de funcionar bien. Si un usuario llega al servidor A, ve el dato actualizado, pero si la siguiente solicitud cae en el servidor B, la información puede estar desactualizada o ausente. Es exactamente aquí donde entra la caché distribuida, una memoria compartida externa que todas las instancias de la aplicación pueden consultar y actualizar de forma centralizada y síncrona.

El papel de Redis en la aceleración de endpoints críticos en NestJS

Redis es una base de datos en memoria extremadamente rápida que opera mediante pares de clave y valor, ideal para almacenar datos temporales con un rendimiento altísimo. Al integrarla con NestJS, un framework modular y organizado para Node.js, logramos crear flujos elegantes para interceptar solicitudes antes de que lleguen a la lógica de negocio compleja. En la práctica, NestJS recibe la llamada HTTP, verifica si la clave correspondiente ya existe en Redis y, si está allí, devuelve la respuesta de inmediato sin consultar PostgreSQL o MySQL.

Para conectar Redis a NestJS de forma limpia, utilizamos bibliotecas maduras como ioredis combinadas con el módulo nativo de caché de la plataforma. Esto permite configurar tiempos de expiración automáticos, conocidos como TTL o Time to Live, asegurando que el dato expire por sí solo después de unos minutos para evitar que información antigua quede atrapada para siempre. Cuando estructuramos esta capa junto con comunicación optimizada entre microservicios, el aumento de rendimiento en endpoints críticos es inmediato y medible.

Cómo implementar interceptores de caché personalizados en NestJS

NestJS facilita enormemente la manipulación del ciclo de vida de las solicitudes a través de interceptores, que actúan como filtros inteligentes capaces de modificar la entrada y salida de un endpoint. Podemos crear un interceptor dedicado que examine la ruta llamada, genere una clave única basada en los parámetros de la URL y consulte a Redis incluso antes de que se ejecute el controlador. Si el dato no existe, el interceptor permite que la solicitud continúe, captura el resultado devuelto por la base de datos, lo guarda en Redis y solo entonces lo entrega al cliente.

A continuación tenemos un ejemplo práctico de un interceptor en TypeScript que realiza esta verificación de forma transparente en cualquier ruta donde sea inyectado:

import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable, of } from 'rxjs';
import { tap } from 'rxjs/operators';
import Redis from 'ioredis';

@Injectable()
export class RedisCacheInterceptor implements NestInterceptor {
  private redis = new Redis();

  async intercept(context: ExecutionContext, next: CallHandler): Promise<Observable<any>> {
    const request = context.switchToHttp().getRequest();
    const cacheKey = `cache:${request.url}`;

    const cachedData = await this.redis.get(cacheKey);
    if (cachedData) {
      return of(JSON.parse(cachedData));
    }

    return next.handle().pipe(
      tap(async (response) => {
        await this.redis.set(cacheKey, JSON.stringify(response), 'EX', 60);
      }),
    );
  }

Este código reduce drásticamente la carga de trabajo en el servidor principal, pero introduce un desafío clásico de la ingeniería de software: ¿qué pasa cuando los datos originales cambian en la base de datos?

Estrategias avanzadas de invalidación para evitar datos fantasma

Guardar información en caché es fácil, pero asegurar que el usuario nunca vea un dato desactualizado tras una modificación es la verdadera prueba de madurez de un sistema. Cuando un registro se actualiza o elimina, la clave correspondiente en Redis debe ser inmediatamente invalidada o actualizada, de lo contrario el sistema seguirá sirviendo la versión antigua. En la práctica, llamamos a esto invalidación basada en eventos, donde cada operación de escritura en la base de datos dispara un comando para borrar la clave obsoleta de Redis.

Un enfoque robusto consiste en publicar eventos de cambio de estado a través de un bus interno o sistema de mensajería, similar a lo que ocurre cuando estructuramos una arquitectura resiliente con circuit breaker para proteger servicios dependientes. De este modo, desacoplamos la lógica de negocio de la limpieza de la caché, asegurando que incluso si la base de datos sufre picos de escritura, la consistencia de los datos devueltos por la API permanezca intacta y confiable para los clientes. Para profundizar en estos conceptos arquitectónicos en producción, consulte también nuestro análisis técnico sobre ¿IA inestable? Cómo usar DSPy para estruturar prompts.

Consideraciones finales sobre rendimiento y escalabilidad

Aplicar caché distribuida con Redis en NestJS transforma la capacidad de atención de una aplicación, reduciendo el tiempo de respuesta de cientos de milisegundos a meros microsegundos. Sin embargo, esta comodidad exige disciplina al nombrar claves, configurar tiempos de expiración correctos e implementar rutinas estrictas de invalidación para evitar errores difíciles de rastrear. Al combinar estas prácticas con un monitoreo constante del uso de memoria, tu API estará lista para escalar con seguridad bajo cualquier volumen de tráfico.