Redis é um armazenamento de dados na memória que acelera drasticamente os aplicativos armazenando em cache consultas caras de banco de dados e respostas de API. Um cache bem colocado pode transformar uma consulta de banco de dados de 200 ms em um acerto de cache de 2 ms. Este guia adiciona cache Redis a um aplicativo Node.js da maneira certa.
📋 Table of Contents
Por que armazenar em cache com Redis?
- Velocidade: As leituras na memória são 10-100x mais rápidas que as consultas ao banco de dados
- Reduza a carga do banco de dados: Atenda solicitações repetidas do cache, poupando seu banco de dados
- Escala: Lide com picos de tráfego sem sobrecarregar seu armazenamento de dados primário
- Versátil: Também lida com sessões, limitação de taxa, filas e pub/sub
Configuração
npm install ioredis
# Run Redis locally with Docker
docker run -d --name redis -p 6379:6379 redis:7-alpine
// redis.js — connection singleton
import Redis from 'ioredis';
export const redis = new Redis({
host: process.env.REDIS_HOST || 'localhost',
port: parseInt(process.env.REDIS_PORT || '6379'),
maxRetriesPerRequest: 3,
retryStrategy: (times) => Math.min(times * 50, 2000),
});
redis.on('error', (err) => console.error('Redis error:', err));
redis.on('connect', () => console.log('Redis connected'));
O padrão Cache-Aside (mais comum)
Verifique o cache primeiro; em caso de erro, busque no banco de dados e preencha o cache:
import { redis } from './redis';
async function getUser(userId) {
const cacheKey = `user:${userId}`;
// 1. Try cache first
const cached = await redis.get(cacheKey);
if (cached) {
return JSON.parse(cached); // cache hit — fast path
}
// 2. Cache miss — fetch from database
const user = await db.users.findById(userId);
if (!user) return null;
// 3. Populate cache with a TTL (expire after 1 hour)
await redis.set(cacheKey, JSON.stringify(user), 'EX', 3600);
return user;
}
Wrapper de cache reutilizável
async function cached(key, ttlSeconds, fetchFn) {
const hit = await redis.get(key);
if (hit) return JSON.parse(hit);
const data = await fetchFn();
if (data !== null && data !== undefined) {
await redis.set(key, JSON.stringify(data), 'EX', ttlSeconds);
}
return data;
}
// Usage — clean and reusable
const products = await cached('products:featured', 600, () =>
db.products.findFeatured()
);
const user = await cached(`user:${id}`, 3600, () =>
db.users.findById(id)
);
Invalidação de cache (a parte difícil)
// When data changes, invalidate the cache to avoid stale reads
async function updateUser(userId, updates) {
const user = await db.users.update(userId, updates);
// Invalidate the cached version
await redis.del(`user:${userId}`);
// Invalidate related caches too
await redis.del('users:list');
return user;
}
// Invalidate by pattern (use SCAN, not KEYS, in production)
async function invalidatePattern(pattern) {
const stream = redis.scanStream({ match: pattern, count: 100 });
stream.on('data', (keys) => {
if (keys.length) redis.del(...keys);
});
}
// invalidatePattern('user:*'); // clear all user caches
Middleware expresso para cache de rota
function cacheRoute(ttlSeconds) {
return async (req, res, next) => {
const key = `route:${req.originalUrl}`;
const cached = await redis.get(key);
if (cached) {
res.setHeader('X-Cache', 'HIT');
return res.json(JSON.parse(cached));
}
// Override res.json to cache the response
const originalJson = res.json.bind(res);
res.json = (body) => {
redis.set(key, JSON.stringify(body), 'EX', ttlSeconds);
res.setHeader('X-Cache', 'MISS');
return originalJson(body);
};
next();
};
}
// Cache this route for 5 minutes
app.get('/api/products', cacheRoute(300), getProducts);
Limitação de taxa com Redis
async function rateLimit(req, res, next) {
const key = `ratelimit:${req.ip}`;
const limit = 100; // requests
const window = 60; // seconds
const current = await redis.incr(key);
if (current === 1) {
await redis.expire(key, window); // set TTL on first request
}
if (current > limit) {
return res.status(429).json({ error: 'Too many requests' });
}
res.setHeader('X-RateLimit-Remaining', Math.max(0, limit - current));
next();
}
Práticas recomendadas de armazenamento em cache
- Sempre defina um TTL: Impede que dados obsoletos vivam para sempre e controla a memória
- Armazene em cache o que é caro e lido com frequência: Não armazene tudo em cache — armazene em cache os caminhos quentes
- Lide com falhas de cache normalmente: Se o Redis estiver inativo, acesse o banco de dados, não trave
- Use nomenclatura de chave consistente:
entity:id:fieldconvenções tornam a invalidação previsível - Evite o comando KEYS em produção: Ele bloqueia Redis – use SCAN
- Fique atento às debandadas de cache: Quando uma chave popular expira, muitas solicitações chegam ao banco de dados de uma só vez — use bloqueios ou TTLs escalonados
Perguntas Frequentes
P: O que NÃO devo armazenar em cache?
R: Dados altamente voláteis que mudam a cada solicitação, dados confidenciais sem segurança adequada e dados baratos para serem computados. Armazene em cache os dados caros, lidos com frequência e relativamente estáveis.
P: Como escolho um TTL?
R: Equilibre frescor versus desempenho. Perfis de usuário: 1 hora. Listagens de produtos: 5 a 10 minutos. Dados em tempo real: segundos ou não armazenados em cache. Combine o TTL com a frequência com que os dados realmente mudam.
P: O que acontece se o Redis falhar?
R: Seu aplicativo deve ser degradado normalmente – detecte erros do Redis e caia no banco de dados. Nunca deixe que uma falha de cache derrube seu aplicativo. Envolva chamadas de cache em try/catch.
P: Redis versus cache na memória (como um mapa JS)?
R: Os caches de mapas na memória são por processo (perdidos na reinicialização, não compartilhados entre instâncias). O Redis é compartilhado entre todas as instâncias do seu aplicativo e persiste – essencial para aplicativos dimensionados horizontalmente.
P: Como evito debandadas de cache?
R: Use um bloqueio (SET NX) para que apenas uma solicitação atualize o cache enquanto outras aguardam ou fornecem dados obsoletos, ou adicione instabilidade aleatória aos TTLs para que as chaves populares não expirem todas simultaneamente.
Conclusão
O cache Redis pode transformar o desempenho do seu aplicativo Node.js, transformando consultas lentas ao banco de dados em acessos instantâneos ao cache. O padrão cache-aside com um wrapper reutilizável cobre a maioria das necessidades, o middleware em nível de rota armazena em cache respostas inteiras e a mesma instância do Redis lida com limitação de taxa e sessões. Lembre-se das duas partes difíceis: sempre defina um TTL e invalide o cache quando os dados forem alterados. Lide com falhas do Redis com facilidade e seu aplicativo ficará mais rápido e resiliente ao mesmo tempo.
🔗 Share this article
✍️ Leave a Comment