Por que o cache avançado é uma necessidade para aplicações TypeScript
Em sistemas modernos de alta carga, o cache deixou de ser uma opção — é um elemento obrigatório da arquitetura. Para desenvolvedores TypeScript que trabalham com Node.js, escolher a estratégia de cache correta pode reduzir o tempo de resposta da API de 200 ms para 2 ms e diminuir a carga no banco de dados em 80-90%. Neste artigo, abordaremos três abordagens de cache em nível avançado: Redis, Memcached e soluções integradas em TypeScript.
1. Cache integrado em TypeScript: do simples Map ao cache LRU
Soluções integradas são ideais para aplicações monolíticas ou microsserviços onde não é necessário um cache distribuído. A opção mais primitiva é usar Map, mas para produção ela não é adequada devido à falta de TTL e controle de memória.
Implementação de cache LRU com TTL
Vamos considerar uma implementação avançada de cache LRU (Least Recently Used) com limpeza 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; }
// Move a chave para o final (mais recente) 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) { // Remove o elemento mais antigo (primeiro) 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 TTLPadrão avançado: decorador de cache
Para manter o código limpo, usamos decoradores do 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 cara ao banco de dados return db.findUser(id); }}2. Redis: cache distribuído com estruturas de dados avançadas
Redis não é apenas um armazenamento key-value. Seu suporte a estruturas de dados complexas (Hash, List, Set, Sorted Set) abre possibilidades para implementar estratégias avançadas de cache.
Cache com invalidação por tags
Problema: ao atualizar uma entidade, é necessário invalidar vários caches. Solução — tags:
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,