Caching en TypeScript: Redis, Memcached y soluciones integradas (nivel avanzado)

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Por qué el caching avanzado es una necesidad para aplicaciones TypeScript



En los sistemas modernos de alta carga, el caching ha dejado de ser una opción — es un elemento obligatorio de la arquitectura. Para los desarrolladores de TypeScript que trabajan con Node.js, elegir la estrategia de caching correcta puede reducir el tiempo de respuesta de la API de 200 ms a 2 ms y disminuir la carga en la base de datos en un 80-90%. En este artículo, exploraremos tres enfoques de caching a nivel avanzado: Redis, Memcached y soluciones integradas en TypeScript.



1. Caching integrado en TypeScript: desde un simple Map hasta un caché LRU



Las soluciones integradas son ideales para aplicaciones monolíticas o microservicios donde no se requiere un caché distribuido. La opción más primitiva es usar Map, pero no es adecuada para producción debido a la falta de TTL y control de memoria.



Implementación de un caché LRU con TTL



Consideremos una implementación avanzada de un caché LRU (Least Recently Used) con limpieza automática:



interface CacheEntry<T> {  value: T;  expiresAt: number;}

class LRUCache<T> { private cache: Map<string, CacheEntry<T>>; private readonly maxSize: number; private readonly ttlMs: number;

constructor(maxSize: number = 100, ttlMs: number = 60000) { this.cache = new Map(); this.maxSize = maxSize; this.ttlMs = ttlMs; }

get(key: string): T | undefined { const entry = this.cache.get(key); if (!entry) return undefined;

if (Date.now() > entry.expiresAt) { this.cache.delete(key); return undefined; }

// Movemos la clave al final (la más reciente) this.cache.delete(key); this.cache.set(key, entry); return entry.value; }

set(key: string, value: T): void { if (this.cache.has(key)) { this.cache.delete(key); } else if (this.cache.size >= this.maxSize) { // Eliminamos el elemento más antiguo (el primero) const oldestKey = this.cache.keys().next().value; this.cache.delete(oldestKey); }

this.cache.set(key, { value, expiresAt: Date.now() + this.ttlMs, }); }

clear(): void { this.cache.clear(); }}

// Usoconst userCache = new LRUCache<User>(500, 300000); // 500 elementos, 5 minutos de TTL


Patrón avanzado: decorador de caching



Para mantener el código limpio, usamos decoradores de TypeScript:



function Cached(ttlMs: number = 60000) {  const cache = new LRUCache<any>(100, ttlMs);

return function ( target: any, propertyKey: string, descriptor: PropertyDescriptor ) { const originalMethod = descriptor.value;

descriptor.value = async function (...args: any[]) { const key = `${propertyKey}_${JSON.stringify(args)}`; const cached = cache.get(key); if (cached !== undefined) return cached;

const result = await originalMethod.apply(this, args); cache.set(key, result); return result; };

return descriptor; };}

class UserService { @Cached(120000) // 2 minutos async getUser(id: string): Promise<User> { // Consulta costosa a la BD return db.findUser(id); }}


2. Redis: caching distribuido con estructuras de datos avanzadas



Redis no es solo un almacén key-value. Su soporte para estructuras de datos complejas (Hash, List, Set, Sorted Set) abre posibilidades para implementar estrategias de caching avanzadas.



Caching con invalidación por etiquetas



Problema: al actualizar una entidad, es necesario invalidar varios cachés. Solución — etiquetas:



import Redis from 'ioredis';

class TaggedCache { private redis: Redis; private readonly keyPrefix: string;

constructor(redisUrl: string, prefix: string = 'cache') { this.redis = new Redis(redisUrl); this.keyPrefix = prefix; }

async get<T>(key: string,

Blogs

Book Recommendations