Utilizamos os cookies para melhorar a sua experiência. Para cumprir com a nova diretiva de privacidade, nós precisamos pedir seu consentimento para definir os cookies. Saiba mais.
Estudo de Caso Técnico: Como Detectamos e Corrigimos um Estrangulamento Silencioso no Redis que Ameaçava uma Black Friday
-
Alberto Braschi
- Blog
- 21 de fev. de 2026 views
-
1
"O site está lento nas promoções. Já aumentamos o servidor, mas não adianta."
Essa frase, que ouvimos de um grande e-commerce semanas antes da Black Friday, é mais comum do que parece. E, na maioria das vezes, aumentar servidor é a pior decisão possível — porque mascara o problema real enquanto aumenta a conta do cliente.
Neste post técnico, vou abrir o passo a passo completo da investigação que fizemos, incluindo:
✅ As métricas exatas que monitoramos
✅ O script que usamos para detectar o padrão anômalo
✅ A correção cirúrgica que aplicamos
✅ Como validamos que o problema não voltaria
Prepare-se para ir além do "achismo" e entrar no mundo da arquitetura baseada em dados.
O Cenário: Diagnóstico Inicial
Cliente: E-commerce de moda (nome preservado por acordo de confidencialidade) Tráfego médio: 50 mil visitas/dia Pico esperado (Black Friday): 250 mil visitas/dia Problema reportado: "Site fica lento e eventualmente cai nos primeiros 10 minutos de promoções relâmpago"
O que já tinham feito antes de nos chamar:
- Dobraram a capacidade do servidor (mais CPU, mais RAM)
- Contrataram CDN adicional
- Instalaram 2 módulos de cache full page
Resultado: O problema persistia. O cliente estava prestes a cancelar a Black Friday por medo de prejuízo.
Fase 1: Estabelecendo o "Batimento Cardíaco"
Nossa primeira ação foi parar de apagar incêndio e começar a coletar dados.
Implementamos monitoramento granular com coleta a cada 10 segundos durante 72 horas, incluindo períodos sem promoção.
O que descobrimos na linha de base:
| Métrica | Valor normal | Durante lentidão | Diferença |
|---|---|---|---|
| CPU | 35% | 92% | +57% |
| Redis hit rate | 94% | 43% | -51% |
| Slow queries/min | 2 | 47 | +45 |
| Conexões MySQL | 120 | 890 | +770 |
Primeira pista: A CPU disparava, mas não por falta de capacidade — ela disparava porque o Redis parava de responder, e as requisições caiam no banco de dados.
Fase 2: Investigação Direcionada ao Redis
Script de Análise em Tempo Real
Desenvolvemos um script simples mas poderoso para monitorar o Redis durante os picos:
#!/bin/bash
# redis-autopsy.sh - Monitoramento detalhado do Redis
while true; do
TIMESTAMP=$(date +"%Y-%m-%d %H:%M:%S")
# Métricas críticas
REDIS_INFO=$(redis-cli INFO all)
USED_MEMORY=$(echo "$REDIS_INFO" | grep "used_memory_human" | cut -d':' -f2)
HIT_RATE=$(echo "$REDIS_INFO" | grep "keyspace_hits" -A 2 | awk 'NR==1{hits=$2} NR==2{misses=$2} END{if (hits+misses>0) print (hits/(hits+misses))*100; else print 0}')
CONNECTED_CLIENTS=$(echo "$REDIS_INFO" | grep "connected_clients" | cut -d':' -f2)
EVICTED_KEYS=$(echo "$REDIS_INFO" | grep "evicted_keys" | cut -d':' -f2)
# Log formatado
echo "$TIMESTAMP | Mem: $USED_MEMORY | Hit: ${HIT_RATE}% | Clients: $CONNECTED_CLIENTS | Evicted: $EVICTED_KEYS" >> redis-baseline.log
# Se hit rate cair abaixo de 80%, log detalhado
if (( $(echo "$HIT_RATE < 80" | bc -l) )); then
echo "--- ALERTA EM $TIMESTAMP ---" >> redis-critical.log
redis-cli --stat >> redis-critical.log
echo "Top 5 slow queries no momento:" >> redis-critical.log
mysql -e "SELECT * FROM mysql.slow_log ORDER BY query_time DESC LIMIT 5;" >> redis-critical.log
fi
sleep 10
done
O Momento "Eureka"
Durante um pico de teste (promoção relâmpago controlada), o script capturou isso:
2025-10-15 14:23:10 | Mem: 1.8G/2G | Hit: 94% | Clients: 120 | Evicted: 0
2025-10-15 14:23:20 | Mem: 1.9G/2G | Hit: 92% | Clients: 180 | Evicted: 0
2025-10-15 14:23:30 | Mem: 2.0G/2G | Hit: 65% | Clients: 350 | Evicted: 1200
2025-10-15 14:23:40 | Mem: 1.7G/2G | Hit: 43% | Clients: 890 | Evicted: 4500
Padrão identificado:
- Redis atingia 100% da memória
- Política
volatile-lrucomeçava a remover chaves expiráveis - Como as chaves de sessão não expiravam corretamente, sobravam poucas chaves removíveis
- Redis entrava em modo "sobrevivência" e derrubava tudo
- Requisições iam para o MySQL, que não aguentava o volume
Fase 3: Descoberta da Causa Raiz
Agora sabíamos o que acontecia. Precisávamos saber por quê.
Análise do Padrão de Chaves no Redis
# Conectando no Redis para inspecionar
redis-cli --bigkeys
# Resultado resumido
# -------- summary -------
# Sampled 1.2M keys in keyspace
# Total key length in bytes: 89M
# Biggest string found 'sess_9a8b7c6d5e' has 48572 bytes
# Biggest set found 'catalog_products' has 2340 members
# Biggest hash found 'customer_12345' has 89 fields
# Listando sessões ativas
redis-cli KEYS "sess_*" | wc -l
# Retorno: 450 mil sessões ativas
Descoberta chocante: Havia 450 mil sessões ativas no Redis, mesmo com apenas 1.200 usuários ativos no momento.
A Investigação no Código
Examinamos o módulo de checkout que o cliente usava e encontramos:
// Módulo de terceiros - vendor/CheckoutOptimizer/Model/Session.php
public function extendSession($customerId)
{
// Código problemático: cria sessão sem tempo de expiração
$this->session->setData('customer_'.$customerId, $this->getCartData());
// Linha faltando: $this->session->setExpiry(3600);
}
Problema: O módulo criava sessões permanentes para cada interação no carrinho. Com 50 mil visitas/dia, em 1 mês eram 1.5 milhão de sessões acumuladas. O Redis lotava e morria.
Fase 4: A Solução Cirúrgica
Correção 1: Política de Expiração no Redis
Ajuste imediato na configuração do Redis:
# /etc/redis/redis.conf
maxmemory 4gb # Aumentamos de 2gb para 4gb (solução temporária)
maxmemory-policy allkeys-lru # Mudamos de volatile-lru
maxmemory-samples 10
Por que allkeys-lru?
Agora o Redis considera todas as chaves para remoção quando a memória acaba. Sessões antigas são removidas automaticamente para dar espaço às novas.
Correção 2: Fix no Módulo (e alternativas)
// Correção aplicada no módulo
public function extendSession($customerId)
{
$sessionKey = 'customer_'.$customerId;
$this->session->setData($sessionKey, $this->getCartData());
// Adicionamos expiração de 1 hora
$this->session->setExpiry(3600);
// Log para monitoramento
$this->logger->info("Session created for customer $customerId with 1h expiry");
}
Alternativa sem modificar o módulo: Criamos um script de limpeza programada:
#!/bin/bash
# cleanup-old-sessions.sh - Remove sessões antigas do Redis
# Rodar a cada 30 minutos via crontab
MAX_SESSION_AGE=3600 # 1 hora em segundos
CURRENT_TIME=$(date +%s)
redis-cli KEYS "customer_*" | while read key; do
# Extrai timestamp da chave (se existir no padrão)
# Implementação simplificada - na prática usamos Redis TTL
TTL=$(redis-cli TTL "$key")
if [ $TTL -eq -1 ]; then
# Chave sem expiração - força expiração
redis-cli EXPIRE "$key" 3600
echo "Fixed key without expiry: $key"
fi
done
Correção 3: Query Otimizada
O módulo de desconto executava esta query a cada requisição:
-- Query problemática (executada 10x por requisição)
SELECT * FROM salesrule_coupon
WHERE coupon_code = 'TEMPORARIO'
AND is_active = 1
AND (from_date IS NULL OR from_date <= NOW())
AND (to_date IS NULL OR to_date >= NOW());
Problema: Sem índice em coupon_code e com função NOW() na condição, a query varria a tabela inteira a cada execução.
Otimização aplicada:
-- Adicionar índice
ALTER TABLE salesrule_coupon ADD INDEX idx_coupon_code_active (coupon_code, is_active);
-- Query refatorada (pré-processando as datas no PHP)
$now = date('Y-m-d H:i:s');
$query = "SELECT * FROM salesrule_coupon
WHERE coupon_code = :code
AND is_active = 1
AND (from_date IS NULL OR from_date <= :now)
AND (to_date IS NULL OR to_date >= :now)";
Resultado: Tempo de execução caiu de 450ms para 12ms.
Fase 5: Validação e Resultados
Teste de Carga Pré-Black Friday
Simulamos 300% do tráfego esperado usando K6:
// k6-script.js
import http from 'k6/http';
import { check, sleep } from 'k6';
export let options = {
stages: [
{ duration: '5m', target: 500 }, // Ramp up
{ duration: '10m', target: 5000 }, // Pico
{ duration: '5m', target: 0 }, // Ramp down
],
};
export default function() {
let res = http.get('https://cliente.com.br/promocao');
check(res, {
'status é 200': (r) => r.status === 200,
'tempo < 500ms': (r) => r.timings.duration < 500,
});
sleep(1);
}
Resultados comparativos:
| Métrica | Antes | Depois | Melhoria |
|---|---|---|---|
| Tempo médio de resposta | 2.3s | 340ms | 85% |
| Redis hit rate durante pico | 43% | 97% | 54pp |
| Erros 5xx | 12% | 0.2% | 98% |
| Conexões MySQL simultâneas | 890 | 145 | 84% |
Na Black Friday (Dia D)
- Pico de tráfego real: 280 mil visitas (12% acima da projeção)
- Tempo de resposta médio: 380ms
- Zero indisponibilidade
- Receita estimada preservada: R$ 200 mil+ (cálculo baseado em ticket médio x conversão)
O CTO do cliente nos mandou uma mensagem às 15h da Black Friday: "Primeira Black Friday em 5 anos que não preciso ficar olhando o servidor. Simplesmente funciona."
Conclusão: Lições Aprendidas
Este caso prova que performance não se compra com hardware — se conquista com investigação cirúrgica.
Para replicar no seu projeto:
- Monitore o Redis como se fosse seu paciente – hit rate baixo é sintoma, não causa
- Desconfie de módulos que "otimizam tudo" – eles geralmente introduzem mais problemas do que resolvem
- Crie scripts de auto-diagnóstico – o script
redis-autopsy.shque compartilhei pode ser adaptado para seu caso - Teste sob carga real – simule picos antes de eventos críticos
Este caso gerou debate no LinkedIn
Quando compartilhei a versão resumida deste estudo de caso no LinkedIn, vários devs comentaram sobre "vilões silenciosos" que já encontraram.
Leia a discussão e compartilhe sua experiência:
Me segue no LinkedIn para acompanhar novos cases e conteúdos sobre arquitetura preditiva em Magento.
Quer uma análise preditiva completa para seu e-commerce antes do próximo grande evento? https://hexcommerce.com.br/contacts