Quando usar ElastiCache Redis: resolvendo lentidão no RDS com camada de cache
Você abre o painel de métricas do RDS às 14h de uma sexta-feira e vê o ReadIOPS no teto, latência de queries subindo para 800ms, e a aplicação começando a retornar timeouts. O banco está respondendo as mesmas perguntas centenas de vezes por segundo — dados de catálogo, sessões de usuário, resultados de busca que não mudam entre uma requisição e outra. Adicionar uma camada de ElastiCache Redis entre a aplicação e o RDS é a intervenção mais direta para esse padrão de problema.
TL;DR — Quando o ElastiCache Redis resolve o problema de lentidão no RDS
| Situação | ElastiCache Redis ajuda? | Motivo |
|---|---|---|
| Mesmas queries de leitura repetidas com alta frequência | ✅ Sim | Resultado servido da memória, sem tocar o RDS |
| Dados que mudam raramente (catálogo, configurações) | ✅ Sim | TTL longo, hit rate alto |
| Sessões de usuário e tokens de autenticação | ✅ Sim | Estrutura de dados nativa (Hash, String) |
| Queries complexas com JOINs pesados e resultado estável | ✅ Sim | Evita reprocessamento no banco |
| Escritas frequentes com leitura imediata do mesmo dado | ⚠️ Cuidado | Invalidação de cache exige lógica explícita |
| Dados financeiros que exigem consistência forte | ❌ Não recomendado | Cache pode servir dado desatualizado |
Como o ElastiCache Redis funciona como camada de cache para o RDS
O Redis é um store de dados em memória que opera como um dicionário chave-valor de alta performance. No contexto de cache para RDS, o padrão mais comum é o cache-aside (também chamado de lazy loading): a aplicação consulta o Redis primeiro; se o dado não estiver lá (cache miss), ela busca no RDS, armazena o resultado no Redis com um TTL definido, e retorna para o cliente. Nas próximas requisições idênticas, o Redis responde diretamente.
O ElastiCache for Redis é o serviço gerenciado da AWS que provisiona, escala, faz patch e monitora clusters Redis sem que você precise operar a infraestrutura subjacente. Ele suporta dois modos de implantação relevantes para esse cenário: Cluster Mode Disabled (um shard com réplicas de leitura) e Cluster Mode Enabled (múltiplos shards para escala horizontal). Para a maioria dos casos de cache de leitura do RDS, o modo sem cluster com uma ou duas réplicas já resolve.
(Memória)"] RDS["RDS PostgreSQL / MySQL
(Disco)"] Client -->|"1. GET product:42"| Redis Redis -->|"2a. Cache HIT retorna dado"| Client Redis -->|"2b. Cache MISS retorna nil"| Client Client -->|"3. Query SQL (apenas em MISS)"| RDS RDS -->|"4. Resultado"| Client Client -->|"5. SETEX product:42 TTL"| Redis
- Requisição chega na aplicação — antes de qualquer query SQL, a aplicação monta a chave de cache (ex:
product:42) e consulta o Redis. - Cache Hit — Redis retorna o dado serializado em memória. O RDS não é consultado. Latência típica em sub-milissegundo dentro da mesma VPC.
- Cache Miss — Redis retorna nil. A aplicação executa a query no RDS, recebe o resultado, grava no Redis com TTL, e retorna ao cliente.
- Expiração (TTL) — Após o TTL, a chave é removida automaticamente. A próxima requisição para aquela chave sofre um cache miss e repopula o cache.
- Invalidação explícita — Quando um dado é atualizado no RDS, a aplicação deve deletar (ou atualizar) a chave correspondente no Redis para evitar servir dados obsoletos.
Configurando o ElastiCache Redis na prática
Antes de qualquer coisa: o cluster Redis precisa estar na mesma VPC que o RDS e as instâncias da aplicação. Tráfego entre VPCs via peering funciona, mas adiciona latência e complexidade desnecessária para esse caso de uso.
1. Criar o Subnet Group
O subnet group define em quais subnets privadas o ElastiCache vai provisionar os nós. Use subnets privadas — nunca exponha o Redis à internet.
aws elasticache create-cache-subnet-group \
--cache-subnet-group-name meu-redis-subnet-group \
--cache-subnet-group-description 'Subnet group para cache Redis' \
--subnet-ids subnet-0abc123456789def0 subnet-0def987654321abc0 \
--region us-east-1
2. Criar o Security Group para o Redis
O Security Group do Redis deve aceitar conexões na porta 6379 apenas a partir do Security Group da aplicação. Isso evita que qualquer recurso na VPC acesse o cache diretamente.
aws ec2 create-security-group \
--group-name redis-sg \
--description 'Security Group para ElastiCache Redis' \
--vpc-id vpc-0123456789abcdef0 \
--region us-east-1
aws ec2 authorize-security-group-ingress \
--group-id sg-0redis1234567890a \
--protocol tcp \
--port 6379 \
--source-group sg-0app1234567890b \
--region us-east-1
3. Criar o Cluster Redis (Cluster Mode Disabled)
Para cache de leitura do RDS, um replication group com uma réplica já oferece alta disponibilidade com failover automático. O parâmetro --automatic-failover-enabled requer pelo menos uma réplica.
aws elasticache create-replication-group \
--replication-group-id meu-redis-rg \
--replication-group-description 'Cache para RDS' \
--cache-node-type cache.t4g.medium \
--engine redis \
--engine-version 7.0 \
--num-cache-clusters 2 \
--cache-subnet-group-name meu-redis-subnet-group \
--security-group-ids sg-0redis1234567890a \
--automatic-failover-enabled \
--at-rest-encryption-enabled \
--transit-encryption-enabled \
--region us-east-1
Pricing e limites de instância variam — consulte sempre a documentação oficial da AWS para valores atualizados.
4. Implementar o padrão cache-aside na aplicação
O exemplo abaixo usa Python com a biblioteca redis-py. O mesmo padrão se aplica a qualquer linguagem.
🔽 [Clique para expandir] — Exemplo Python: cache-aside com Redis e RDS
import redis
import json
import psycopg2
# Conexão com ElastiCache Redis (use o endpoint do Primary)
redis_client = redis.Redis(
host='meu-redis-rg.abc123.ng.0001.use1.cache.amazonaws.com',
port=6379,
ssl=True, # transit-encryption-enabled exige SSL
decode_responses=True
)
# Conexão com RDS (exemplo PostgreSQL)
def get_db_connection():
return psycopg2.connect(
host='meu-rds.cluster-abc123.us-east-1.rds.amazonaws.com',
database='minha_base',
user='app_user',
password='senha_segura'
)
def get_product(product_id: int) -> dict:
cache_key = f'product:{product_id}'
ttl_seconds = 300 # 5 minutos
# 1. Tenta o cache primeiro
cached = redis_client.get(cache_key)
if cached:
return json.loads(cached) # Cache hit
# 2. Cache miss — busca no RDS
conn = get_db_connection()
cursor = conn.cursor()
cursor.execute(
'SELECT id, name, price, category FROM products WHERE id = %s',
(product_id,)
)
row = cursor.fetchone()
cursor.close()
conn.close()
if row is None:
return None
product = {
'id': row[0],
'name': row[1],
'price': float(row[2]),
'category': row[3]
}
# 3. Armazena no cache com TTL
redis_client.setex(cache_key, ttl_seconds, json.dumps(product))
return product
def update_product(product_id: int, new_price: float):
conn = get_db_connection()
cursor = conn.cursor()
cursor.execute(
'UPDATE products SET price = %s WHERE id = %s',
(new_price, product_id)
)
conn.commit()
cursor.close()
conn.close()
# 4. Invalida o cache após atualização
cache_key = f'product:{product_id}'
redis_client.delete(cache_key)
IAM e permissões necessárias
O ElastiCache Redis não usa IAM para autenticação de conexão de dados por padrão — o controle de acesso é feito via Security Groups e, opcionalmente, via Redis AUTH (token de senha) ou RBAC com usuários do ElastiCache (disponível no Redis 6.x+). Para operações de gerenciamento via API/CLI, a role da aplicação precisa de permissões específicas.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ElastiCacheDescribe",
"Effect": "Allow",
"Action": [
"elasticache:DescribeReplicationGroups",
"elasticache:DescribeCacheClusters"
],
"Resource": "*"
}
]
}
Conexões de dados ao Redis (GET, SET, DEL) não passam pelo IAM — são controladas exclusivamente por Security Groups e autenticação no nível do Redis. Verifique a documentação do ElastiCache RBAC para configurar usuários com permissões granulares se o seu modelo de segurança exigir.
Monitorando a eficácia do cache com CloudWatch
Depois de ativar o cache, a métrica mais importante é o CacheHitRate — disponível no namespace AWS/ElastiCache no CloudWatch. Um hit rate abaixo de 80% geralmente indica TTL muito curto, chaves mal estruturadas, ou um padrão de acesso muito disperso para que o cache seja eficaz.
aws cloudwatch get-metric-statistics \
--namespace AWS/ElastiCache \
--metric-name CacheHitRate \
--dimensions Name=ReplicationGroupId,Value=meu-redis-rg \
--start-time 2024-01-15T00:00:00Z \
--end-time 2024-01-15T23:59:59Z \
--period 3600 \
--statistics Average \
--region us-east-1
Monitore também CurrConnections e Evictions. Evictions altas indicam que o nó está sem memória e removendo chaves antes do TTL — sinal de que o tipo de instância precisa ser aumentado ou que o padrão de chaves precisa ser revisado.
- CacheHitRate — percentual de requisições respondidas pelo Redis sem tocar o RDS. Alvo: acima de 80% para justificar o cache.
- CurrConnections — conexões ativas no momento. Picos podem indicar connection leak na aplicação.
- Evictions — chaves removidas por falta de memória antes do TTL expirar. Zero é o ideal.
- ReplicationLag — atraso entre o nó primário e as réplicas. Relevante se você direcionar leituras para réplicas.
- DatabaseMemoryUsagePercentage — uso de memória do Redis. Acima de 80% é sinal de alerta.
O erro clássico: invalidação de cache esquecida
Aqui está o padrão de falha que aparece em produção com mais frequência nesse setup:
O time implementa o cache-aside corretamente para leituras. Hit rate sobe para 95%. Tudo parece ótimo. Três semanas depois, um cliente abre um ticket dizendo que o preço exibido no site está errado — mostra R$ 49,90 quando o correto é R$ 39,90 após uma promoção.
A investigação revela que o UPDATE no banco foi feito diretamente via script SQL de manutenção, sem passar pelo código da aplicação. O cache nunca foi invalidado. O Redis continuou servindo o preço antigo até o TTL expirar — que estava configurado para 24 horas.
A correção não é técnica, é de processo: qualquer caminho que escreva no RDS precisa também invalidar o cache correspondente. Isso inclui scripts de migração, jobs de background, e ferramentas de administração. Se isso não for possível garantir, reduza o TTL para um valor que o negócio tolere como 'dado potencialmente desatualizado'.
Cache é um contrato de consistência eventual. Você está dizendo explicitamente: 'aceito que esse dado pode estar desatualizado por até N segundos.' Se o negócio não aceita isso para um determinado dado, esse dado não deve ser cacheado.
Escolhendo o TTL certo para cada tipo de dado
Não existe um TTL universal. A escolha depende de com que frequência o dado muda no RDS e qual o impacto de servir um dado desatualizado.
| Tipo de dado | TTL sugerido | Justificativa |
|---|---|---|
| Configurações de sistema | 1-6 horas | Mudam raramente, impacto baixo de desatualização |
| Catálogo de produtos (nome, descrição) | 15-60 minutos | Mudanças são planejadas com antecedência |
| Preços e estoque | 1-5 minutos | Mudanças frequentes, impacto alto de desatualização |
| Sessões de usuário | Duração da sessão | Invalidação explícita no logout |
| Resultados de busca | 30 segundos a 5 minutos | Depende da frequência de atualização do índice |
| Dados financeiros / saldos | Não cachear | Consistência forte necessária |
Quando o ElastiCache Redis não resolve o problema de lentidão no RDS
Cache resolve lentidão causada por leituras repetidas dos mesmos dados. Se a lentidão vem de outro lugar, adicionar Redis não vai ajudar — e pode mascarar o problema real por um tempo.
Verifique antes de investir no cache:
- Queries sem índice — um full table scan em uma tabela de 50 milhões de linhas vai ser lento mesmo com cache, porque o cache miss ainda vai executar a query completa. Verifique o
Performance Insightsno RDS para identificar queries com alto db load. - Escritas pesadas — se o gargalo é em INSERTs/UPDATEs, cache de leitura não ajuda. Considere Read Replicas do RDS para separar carga de leitura e escrita.
- Conexões esgotadas — se o RDS está rejeitando conexões por ter atingido
max_connections, o problema é connection pooling (RDS Proxy), não cache. - Dados altamente dinâmicos — se cada requisição busca dados únicos (ex: relatório personalizado por usuário com filtros arbitrários), o hit rate do cache será próximo de zero e o overhead de verificar o Redis a cada requisição vai piorar a latência.
Próximos passos e documentação oficial
Com o ElastiCache Redis configurado e o padrão cache-aside implementado, os próximos pontos a endereçar são: definir alarmes no CloudWatch para Evictions e CacheHitRate, revisar o tamanho do nó com base no uso real de memória após a primeira semana de produção, e avaliar se o RDS Proxy complementa a solução para gerenciar connection pooling.
- Documentação oficial do ElastiCache for Redis
- Best Practices para ElastiCache
- RDS Performance Insights
- RDS Proxy — gerenciamento de conexões
Glossário
| Termo | Definição |
|---|---|
| Cache-aside (Lazy Loading) | Padrão onde a aplicação verifica o cache antes do banco. Em caso de miss, busca no banco e popula o cache. |
| TTL (Time to Live) | Tempo em segundos após o qual uma chave no Redis é automaticamente removida. |
| Cache Hit / Cache Miss | Hit: dado encontrado no cache. Miss: dado não encontrado, necessário buscar na fonte primária. |
| Eviction | Remoção de chaves pelo Redis quando a memória disponível é insuficiente, antes do TTL expirar. |
| Replication Group | Conjunto de nós Redis no ElastiCache composto por um primário e uma ou mais réplicas de leitura. |
Comentários
Postar um comentário