Cache em TypeScript: Redis, Memcached e soluções integradas (nível avançado)

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course

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 TTL


Padrã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,

Blogs

Book Recommendations