Redis para AWS: Guia Completo de Deploy
Quando falamos de performance em aplicações modernas, Redis aparece como peça central em praticamente toda arquitetura que precisa de respostas abaixo de milissegundos. Quando combinamos o poder do Redis com a infraestrutura da AWS, ganhamos um arsenal completo para resolver problemas de cache, sessões, filas e comunicação em tempo real. Neste guia, vou cobrir desde a criação do cluster até padrões de código em TypeScript que você pode levar direto para produção.
Se você já trabalha com desenvolvimento há pelo menos dois anos e quer entender como Redis e AWS se encaixam no seu dia a dia de infraestrutura, este post é para você.
Por Que Usar Redis na AWS com ElastiCache
Rodar Redis por conta própria em uma instância EC2 é perfeitamente possível, mas traz uma lista de responsabilidades que consome tempo: patching, backups, failover, monitoramento de memória e replicação. O Amazon ElastiCache for Redis elimina essas tarefas operacionais e entrega um serviço gerenciado com alta disponibilidade nativa.
Existem três razões principais para essa combinação fazer sentido.
Integração Nativa com o Ecossistema AWS
O ElastiCache vive dentro da sua VPC, o que significa que a comunicação entre suas aplicações (rodando em ECS, EKS, Lambda ou EC2) e o Redis acontece em rede privada com latência mínima. Além disso, o IAM, o CloudWatch e o Secrets Manager se integram de forma natural, facilitando governança e observabilidade.
Escalabilidade Sem Dor de Cabeça
Com ElastiCache, você pode escalar verticalmente (trocando o tipo de nó) ou horizontalmente (adicionando réplicas de leitura e usando cluster mode). Isso é especialmente útil em cenários de Black Friday ou picos sazonais, onde a carga pode triplicar em minutos.
Casos de Uso Reais Onde Essa Combinação Brilha
Na prática, vejo Redis na AWS resolver problemas concretos em diversos contextos: cache de consultas frequentes em APIs REST que atendem milhões de requisições por dia, armazenamento de sessões em aplicações distribuídas rodando em múltiplos containers, rate limiting inteligente para proteger APIs públicas (tema que abordamos em detalhes no post sobre Rate Limiting Inteligente com Redis e Node.js), filas leves com Redis Streams para processamento assíncrono, e leaderboards em tempo real para plataformas de gamificação.
Setup do ElastiCache for Redis na AWS
Vamos criar um cluster Redis usando a AWS CLI. Esse approach é reproduzível e pode ser integrado em pipelines de CI/CD, algo que discutimos no post sobre DevOps para Devs: Docker, CI/CD, Kubernetes e AWS.
Criando o Subnet Group
Primeiro, precisamos de um subnet group que define em quais subnets da VPC o Redis vai operar:
// Comando AWS CLI para criar o subnet group
// Execute no terminal ou integre no seu script de infra
/*
aws elasticache create-cache-subnet-group \
--cache-subnet-group-name redis-prod-subnet \
--cache-subnet-group-description "Subnets privadas para Redis em producao" \
--subnet-ids subnet-0a1b2c3d4e5f00001 subnet-0a1b2c3d4e5f00002
*/Criando o Cluster Redis com Replicação
Para produção, sempre use Multi-AZ com pelo menos uma réplica de leitura:
/*
aws elasticache create-replication-group \
--replication-group-id redis-prod-cluster \
--replication-group-description "Redis cluster de producao" \
--engine redis \
--engine-version 7.1 \
--cache-node-type cache.r7g.large \
--num-cache-clusters 3 \
--cache-subnet-group-name redis-prod-subnet \
--security-group-ids sg-0a1b2c3d4e5f00001 \
--automatic-failover-enabled \
--multi-az-enabled \
--at-rest-encryption-enabled \
--transit-encryption-enabled \
--snapshot-retention-limit 7 \
--snapshot-window 03:00-05:00 \
--preferred-maintenance-window sun:05:00-sun:07:00
*/Alguns detalhes importantes sobre essa configuração: o tipo cache.r7g.large usa instâncias Graviton3, que oferecem melhor relação custo/desempenho. A opção num-cache-clusters 3 cria um nó primário e duas réplicas distribuídas em AZs diferentes. As flags de encryption garantem dados criptografados em trânsito e em repouso. O snapshot diário com retenção de 7 dias fornece a rede de segurança que toda aplicação em produção precisa.
Configurando o Security Group
O security group deve permitir apenas tráfego na porta 6379 vindo dos seus serviços:
/*
aws ec2 authorize-security-group-ingress \
--group-id sg-0a1b2c3d4e5f00001 \
--protocol tcp \
--port 6379 \
--source-group sg-0a1b2c3d4e5f00099
*/Note que estamos referenciando outro security group como origem, e não um CIDR. Essa é a forma correta de liberar acesso entre serviços na mesma VPC.
Exemplo Prático: Cache Layer com TypeScript e ioredis
Agora que temos a infraestrutura provisionada, vamos construir uma camada de cache robusta em TypeScript. Uso a biblioteca ioredis por ser a mais completa para ambientes de produção, com suporte nativo a clusters, sentinels e reconexão automática.
Conexão e Configuração do Cliente Redis
import Redis from "ioredis";
interface RedisConfig {
host: string;
port: number;
tls: boolean;
maxRetriesPerRequest: number;
retryDelayMs: number;
}
function createRedisClient(config: RedisConfig): Redis {
const client = new Redis({
host: config.host,
port: config.port,
tls: config.tls ? {} : undefined,
maxRetriesPerRequest: config.maxRetriesPerRequest,
retryStrategy(times: number): number {
const delay = Math.min(times * config.retryDelayMs, 5000);
console.log(
`Redis: tentativa de reconexao #${times}, aguardando ${delay}ms`
);
return delay;
},
reconnectOnError(err: Error): boolean {
const targetErrors = ["READONLY", "ECONNRESET"];
return targetErrors.some((target) => err.message.includes(target));
},
});
client.on("connect", () => {
console.log("Redis: conexao estabelecida");
});
client.on("error", (err: Error) => {
console.error("Redis: erro na conexao:", err.message);
});
return client;
}
const redis = createRedisClient({
host:
process.env.REDIS_HOST ||
"redis-prod-cluster.abc123.ng.0001.use1.cache.amazonaws.com",
port: 6379,
tls: true,
maxRetriesPerRequest: 3,
retryDelayMs: 200,
});
// Camada de cache genérica com tipagem forte
async function cacheGet<T>(
key: string,
fetcher: () => Promise<T>,
ttlSeconds: number = 300
): Promise<T> {
const cached = await redis.get(key);
if (cached !== null) {
console.log(`Cache HIT: ${key}`);
return JSON.parse(cached) as T;
}
console.log(`Cache MISS: ${key}`);
const freshData = await fetcher();
await redis.set(key, JSON.stringify(freshData), "EX", ttlSeconds);
return freshData;
}
// Invalidação de cache por padrão
async function cacheInvalidate(pattern: string): Promise<number> {
let cursor = "0";
let deletedCount = 0;
do {
const [nextCursor, keys] = await redis.scan(
cursor,
"MATCH",
pattern,
"COUNT",
100
);
cursor = nextCursor;
if (keys.length > 0) {
const pipeline = redis.pipeline();
keys.forEach((key) => pipeline.del(key));
await pipeline.exec();
deletedCount += keys.length;
}
} while (cursor !== "0");
console.log(
`Cache invalidado: ${deletedCount} chaves removidas com padrao "${pattern}"`
);
return deletedCount;
}
export { redis, cacheGet, cacheInvalidate };Três pontos importantes nesse código: a retryStrategy usa backoff exponencial com teto de 5 segundos, evitando que o cliente fique preso em loops de reconexão. O reconnectOnError trata especificamente o cenário de failover do ElastiCache, onde o nó primário pode ficar temporariamente em modo READONLY. A função cacheInvalidate usa SCAN ao invés de KEYS, o que é essencial em produção para não bloquear o Redis.
Exemplo Prático: API com Cache e Sessões
import express, { Request, Response, NextFunction } from "express";
import { redis, cacheGet, cacheInvalidate } from "./redis-client";
const app = express();
app.use(express.json());
// Tipos da aplicação
interface Product {
id: string;
name: string;
price: number;
category: string;
}
interface UserSession {
userId: string;
email: string;
role: string;
createdAt: number;
}
// Middleware de sessão usando Redis
async function sessionMiddleware(
req: Request,
res: Response,
next: NextFunction
): Promise<void> {
const sessionId = req.headers["x-session-id"] as string;
if (!sessionId) {
res.status(401).json({ error: "Sessao nao fornecida" });
return;
}
const sessionKey = `session:${sessionId}`;
const sessionData = await redis.get(sessionKey);
if (!sessionData) {
res.status(401).json({ error: "Sessao expirada ou invalida" });
return;
}
// Renova o TTL da sessão a cada requisição (sliding expiration)
await redis.expire(sessionKey, 1800); // 30 minutos
(req as any).session = JSON.parse(sessionData) as UserSession;
next();
}
// Simulação de busca no banco de dados
async function fetchProductsFromDatabase(
category: string
): Promise<Product[]> {
// Aqui entraria a query real ao RDS/DynamoDB
console.log(`Consultando banco de dados para categoria: ${category}`);
return [
{
id: "prod-001",
name: "Teclado Mecânico",
price: 450.0,
category,
},
{
id: "prod-002",
name: "Mouse Ergonômico",
price: 280.0,
category,
},
];
}
// Endpoint com cache inteligente
app.get(
"/api/products/:category",
sessionMiddleware,
async (req: Request, res: Response) => {
const { category } = req.params;
const cacheKey = `products:category:${category}`;
const products = await cacheGet<Product[]>(
cacheKey,
() => fetchProductsFromDatabase(category),
600 // Cache de 10 minutos
);
res.json({
data: products,
meta: {
category,
count: products.length,
},
});
}
);
// Endpoint para criar produto (com invalidação de cache)
app.post(
"/api/products",
sessionMiddleware,
async (req: Request, res: Response) => {
const product = req.body as Product;
// Salva no banco de dados (simulado)
console.log(`Produto salvo: ${product.name}`);
// Invalida o cache da categoria afetada
await cacheInvalidate(`products:category:${product.category}*`);
// Publica evento para outros serviços via Redis Pub/Sub
await redis.publish(
"product:created",
JSON.stringify({
productId: product.id,
timestamp: Date.now(),
})
);
res.status(201).json({ data: product });
}
);
// Health check que verifica a conexão com Redis
app.get("/health", async (_req: Request, res: Response) => {
try {
const start = Date.now();
await redis.ping();
const latency = Date.now() - start;
res.json({
status: "healthy",
redis: {
connected: true,
latencyMs: latency,
},
});
} catch (error) {
res.status(503).json({
status: "unhealthy",
redis: { connected: false },
});
}
});
app.listen(3000, () => {
console.log("Servidor rodando na porta 3000");
});Esse exemplo demonstra três padrões que uso constantemente em produção: cache-aside com TTL para leitura de catálogo, sessões distribuídas com sliding expiration, e invalidação cirúrgica de cache quando dados são modificados.
Boas Práticas de Redis na AWS para Produção
Monitoramento e Alertas com CloudWatch
Configure alarmes para as métricas mais críticas do ElastiCache:
/*
aws cloudwatch put-metric-alarm \
--alarm-name "redis-high-memory-usage" \
--metric-name DatabaseMemoryUsagePercentage \
--namespace AWS/ElastiCache \
--statistic Average \
--period 300 \
--threshold 80 \
--comparison-operator GreaterThanThreshold \
--evaluation-periods 3 \
--alarm-actions arn:aws:sns:us-east-1:123456789012:ops-alerts \
--dimensions Name=CacheClusterId,Value=redis-prod-cluster-001
*/As quatro métricas que você nunca deve ignorar: DatabaseMemoryUsagePercentage (alerte acima de 80%), EngineCPUUtilization (alerte acima de 70%), CurrConnections (alerte quando se aproximar do limite do nó), e ReplicationLag (alerte acima de 1 segundo).
Segurança e Rede
Nunca exponha o ElastiCache para a internet pública. O serviço foi projetado para rodar exclusivamente dentro da VPC. Use o AWS Secrets Manager para armazenar o AUTH token do Redis e rotacione-o periodicamente. Habilite sempre a criptografia em trânsito (TLS) e em repouso, especialmente se sua aplicação lida com dados sensíveis.
Estratégias de Cache que Fazem Diferença
Defina TTLs baseados no padrão de acesso dos
Guias Relacionados
Explore mais guias técnicos
Mais de 100 guias práticos de integração entre frameworks, ferramentas e plataformas.
Ver todos os guias