Uma página de produto consulta o banco em 180 ms. Depois que o resultado passa a ser guardado no Redis, a mesma leitura leva poucos milissegundos. Parece que o problema acabou — até o preço mudar no banco, continuar antigo no cache e ser exibido incorretamente para milhares de pessoas.
Redis pode reduzir latência e aliviar sistemas mais lentos, mas introduz novas decisões: por quanto tempo um valor pode ficar desatualizado? O que acontece quando a memória enche? Uma réplica pode perder a última escrita? O sistema continua funcionando se o Redis cair?
O que é Redis?
Redis é um servidor de estruturas de dados que mantém o conjunto de trabalho principalmente em memória. A aplicação acessa valores por chave e pode executar operações diretamente no servidor sobre diferentes tipos de dados.
Os tipos mais conhecidos incluem:
- Strings: texto, bytes, números, tokens e valores serializados;
- Hashes: conjuntos de campos, úteis para representar objetos pequenos;
- Lists: sequências ordenadas pelas extremidades;
- Sets: coleções sem repetição;
- Sorted sets: itens únicos ordenados por uma pontuação;
- Streams: registros ordenados para processamento de eventos.
Isso diferencia Redis de um cache limitado a guardar objetos opacos. Um contador pode usar INCR, um ranking pode usar sorted sets e uma sessão pode receber expiração sem que a aplicação implemente essas operações em outro banco.
Redis não precisa ser apenas cache. Pode servir sessões, contadores, rate limiting, rankings, coordenação e processamento de eventos. Cada uso, porém, pede garantias diferentes. Perder uma cópia de uma página é bem diferente de perder o único registro de um pagamento.
Como funciona?
Clientes se conectam ao servidor, enviam comandos e recebem respostas. O Redis executa comandos de forma sequencial no seu fluxo principal, o que torna operações individuais como INCR atômicas sem exigir um lock criado pela aplicação.
Aplicação → comando e chave → Redis → operação em memória → resposta
“Sequencial” não significa que toda a implementação possui uma única thread nem que qualquer workflow com vários comandos seja atômico. Persistência, rede e outras tarefas podem usar threads ou processos auxiliares. Além disso, duas chamadas separadas ainda podem ser intercaladas com comandos de outro cliente.
Chaves e estruturas de dados
Uma chave deve revelar seu domínio e sua identidade sem ficar excessivamente longa. Um padrão como catalogo:produto:4821 evita colisões melhor do que apenas 4821.
sessao:usuario:781 → hash com dados da sessão
ranking:semanal → sorted set com pontuações
limite:login:ip:203... → contador com expiração
cache:produto:4821 → representação consultada com frequência
Escolher a estrutura correta importa porque as operações possuem custos diferentes. Ler uma string pequena não tem o mesmo impacto que retornar milhares de itens de uma coleção. Um comando isolado que percorre muitos elementos pode atrasar os demais clientes.
Expiração não é eviction
O TTL determina quanto tempo uma chave deve existir. Quando o prazo termina, ela expira e deixa de ser retornada. É uma regra ligada à validade do dado.
A eviction acontece quando o uso ultrapassa o limite configurado em maxmemory. Nesse momento, a política escolhida decide quais chaves remover ou se novas escritas devem falhar. É uma decisão de capacidade.
TTL venceu → chave expirou por tempo
memória atingiu o limite → política de eviction foi aplicada
Políticas como allkeys-lru favorecem chaves usadas recentemente; allkeys-lfu favorece as mais frequentes; noeviction preserva as chaves existentes e rejeita escritas que precisariam de mais memória. As políticas LRU e LFU usam aproximações eficientes, portanto não representam uma ordenação perfeita de todas as chaves.
Quando utilizar?
Redis é uma boa opção quando o acesso rápido e operações atômicas simples justificam um componente adicional. Casos comuns incluem:
- Cachear leituras caras e repetidas;
- Armazenar sessões com tempo de vida definido;
- Implementar contadores e limites de requisição;
- Manter rankings com sorted sets;
- Coordenar trabalho de curta duração;
- Distribuir eventos em tempo real ou processar streams quando suas garantias atendem ao caso.
Antes de adicioná-lo, pergunte se um índice melhor, uma consulta corrigida, cache HTTP, CDN ou cache local resolve o problema com menos operação. Redis não conserta uma consulta ruim; ele pode apenas esconder seu custo enquanto o cache está aquecido.
Evite usá-lo como única fonte de verdade quando perder dados é inaceitável e o desenho não contempla persistência, replicação, backups e recuperação testada. Também não é a escolha natural para consultas relacionais complexas ou para um conjunto de dados muito maior que a memória disponível.
Exemplo prático: cache-aside em um catálogo
Considere um catálogo no qual 90% das leituras acessam poucos produtos populares. O banco é a fonte de verdade e Redis guarda cópias temporárias.
GET /produtos/4821
│
▼
Redis: cache:produto:4821 existe?
│ sim │ não
▼ ▼
retorna cópia consulta o banco
│
▼
grava no Redis com TTL
│
▼
retorna
Esse padrão é chamado cache-aside. A aplicação controla a leitura e o preenchimento do cache:
produto = redis.get(chave)
se produto não existe:
produto = banco.buscar(id)
redis.set(chave, produto, ttl=5_minutos)
retornar produto
Em uma atualização, a ordem mais segura costuma ser persistir no banco e depois invalidar a chave:
banco.atualizar(produto)
redis.del("cache:produto:4821")
A próxima leitura encontra um miss e repopula o cache a partir da fonte de verdade. Atualizar o banco e a cópia no Redis parece mais eficiente, mas cria duas escritas que podem divergir se apenas uma delas for concluída.
Ainda existe uma janela de inconsistência: a atualização do banco pode funcionar e a invalidação falhar. O TTL limita por quanto tempo a cópia antiga pode sobreviver, mas não oferece consistência imediata. Quando isso for insuficiente, o sistema precisa de uma estratégia mais forte, como eventos confiáveis de invalidação e reconciliação.
Como escolher o TTL?
TTL não deve ser um número copiado de outro sistema. Ele combina requisitos de negócio e comportamento da carga:
- Quanto tempo um dado antigo pode ser exibido?
- Com que frequência a fonte muda?
- Quanto tráfego o banco suporta durante misses?
- Qual hit rate torna o cache útil?
- Quanto custa reconstruir uma entrada?
TTL curto reduz a janela de desatualização, mas derruba o hit rate e aumenta a carga na fonte. TTL longo melhora o aproveitamento do cache, mas permite dados antigos por mais tempo. Adicionar uma pequena variação aleatória ao TTL evita que muitas chaves criadas juntas expirem no mesmo instante.
O cache stampede
Imagine uma chave popular expirando quando mil requisições chegam ao mesmo tempo. Todas encontram um miss e consultam o banco. O pico que o cache deveria absorver atinge a fonte de uma vez: é o cache stampede.
chave expira
├─ requisição 1 ─┐
├─ requisição 2 ─┼─→ mesma consulta cara no banco
├─ requisição 3 ─┤
└─ ... ──────────┘
Algumas formas de reduzir o problema são:
- Permitir que apenas uma requisição reconstrua a chave e fazer as demais aguardarem por pouco tempo;
- Renovar valores populares antes da expiração;
- Servir temporariamente um valor antigo enquanto a atualização ocorre;
- Distribuir expirações com jitter;
- Proteger o banco com limites de concorrência e backpressure.
O lock usado para reconstrução também precisa expirar e ser liberado apenas por quem o adquiriu. Caso contrário, a falha de um processo pode bloquear a chave indefinidamente ou remover o lock de outro processo.
Persistência e durabilidade
Redis oferece opções diferentes para escrever dados em armazenamento durável:
- RDB: cria snapshots do conjunto de dados em determinados momentos;
- AOF: registra operações de escrita para reproduzi-las na inicialização;
- RDB + AOF: combina as duas estratégias;
- Sem persistência: adequado quando todo o conteúdo pode ser reconstruído.
RDB tende a produzir arquivos compactos e reinicializações mais rápidas, mas uma falha pode perder as mudanças posteriores ao último snapshot. AOF reduz essa janela conforme a política de sincronização com o disco, ao custo de mais I/O, arquivos maiores e trabalho de reescrita.
Persistência não substitui backup. Um comando incorreto também pode ser persistido e replicado. Backup protege contra outras classes de problema e só é confiável quando a restauração é testada.
Replicação e alta disponibilidade
Uma instância primária pode enviar suas alterações para réplicas. Elas ajudam a recuperar o serviço após a falha do primário e podem atender determinados padrões de leitura.
Por padrão, a replicação é assíncrona: o primário pode confirmar uma escrita antes que a réplica a tenha processado. Se ocorrer um failover nesse intervalo, a escrita confirmada pode desaparecer.
cliente → escrita no primário → resposta
└────────→ réplica recebe depois
O comando WAIT permite aguardar confirmações de réplicas e reduz a probabilidade de perda, mas não transforma o conjunto em um sistema de consistência forte. A garantia final também depende da persistência e do processo de failover.
Réplicas, Sentinel e Redis Cluster resolvem problemas relacionados, mas diferentes:
- Replicação mantém cópias dos dados;
- Sentinel monitora instâncias e coordena failover em uma topologia não clusterizada;
- Redis Cluster divide as chaves em shards e oferece failover por shard.
Particionar aumenta a capacidade, mas restringe operações que envolvem várias chaves: elas precisam estar no mesmo slot quando um comando exige acesso conjunto. Hot keys também continuam sendo um problema, pois uma única chave muito acessada pertence a um shard específico.
Vantagens
- Oferece baixa latência para dados que cabem no conjunto de trabalho em memória;
- Possui estruturas de dados e operações atômicas úteis no servidor;
- Permite expiração por chave e políticas explícitas para pressão de memória;
- Reduz carga sobre bancos e serviços mais caros;
- Atende vários padrões, de cache e sessões a rankings e streams.
Limitações e trade-offs
| Decisão | Benefício | Custo ou risco |
|---|---|---|
| TTL mais longo | Mais hits e menos carga na fonte | Dados podem ficar antigos por mais tempo |
| Eviction automática | Uso de memória permanece limitado | Uma chave útil pode desaparecer antes do TTL |
| AOF com sincronização frequente | Menor janela de perda | Mais I/O e possível impacto de latência |
| Réplicas assíncronas | Alta disponibilidade com pouca espera na escrita | Failover pode perder escritas ainda não replicadas |
| Cluster com shards | Mais capacidade de memória e throughput | Operações multi-key e hot keys exigem cuidado |
Boas práticas
Defina limites de memória e a política de eviction
Não espere a máquina ficar sem RAM. Configure maxmemory, escolha a política de acordo com o uso e reserve espaço para buffers de replicação, persistência e clientes. Cache e estado não descartável costumam merecer instâncias separadas porque pedem políticas diferentes.
Use timeouts e degradação controlada
Um Redis lento pode prender todas as requisições da aplicação. Configure timeouts, limite retries e decida o comportamento de fallback. Em um cache, muitas vezes é melhor consultar a fonte; em rate limiting ou sessões, falhar aberto ou fechado é uma decisão de negócio e segurança.
Observe o que determina capacidade
Monitore memória usada, fragmentação, hit rate, misses, chaves expiradas, chaves removidas por eviction, latência de comandos, conexões, replicação e erros. Teste também o comportamento com cache frio e durante failover.
Evite chaves e valores descontrolados
Uma chave enorme, uma coleção sem limite ou uma resposta muito grande pode consumir memória e bloquear o servidor por tempo relevante. Defina limites, pagine leituras e prefira comandos cujo custo é conhecido para o tamanho esperado dos dados.
Proteja a instância
Não exponha Redis diretamente à internet. Restrinja rede, use autenticação e autorização, criptografe conexões quando necessário e separe ambientes e aplicações por limites claros.
Erros comuns
Tratar Redis como fonte de verdade sem definir durabilidade
“Está no Redis” não informa a janela aceitável de perda. Documente persistência, replicação, backups, RPO, RTO e o que acontece durante failover.
Colocar TTL e esquecer a invalidação
TTL apenas limita a duração máxima de uma cópia antiga. Quando uma alteração precisa aparecer imediatamente, invalide a chave no fluxo de escrita e trate falhas dessa invalidação.
Usar comandos custosos no caminho crítico
Buscar todas as chaves ou retornar coleções enormes pode aumentar a latência para todos os clientes. Modele o acesso e confira a complexidade dos comandos antes de levar o padrão para produção.
Compartilhar a mesma instância para tudo
Um cache sob pressão pode remover ou atrasar sessões, locks e filas que possuem requisitos diferentes. Separar workloads reduz interferência e permite configurar memória, eviction e persistência de forma coerente.
Ignorar o cache frio
Depois de um restart ou failover, o hit rate pode despencar e sobrecarregar o banco. Meça esse cenário e planeje aquecimento gradual, limites de concorrência ou capacidade temporária na fonte.
Perguntas de entrevista
- Qual é a diferença entre expiração por TTL e eviction por falta de memória?
- Como funciona cache-aside e onde existe uma janela de inconsistência?
- O que é cache stampede e como você o mitigaria?
- Quando escolher RDB, AOF, ambos ou nenhuma persistência?
- Por que uma réplica não garante que toda escrita confirmada sobreviverá a um failover?
- Como escolher entre string, hash, set e sorted set para um requisito?
- O que acontece com o sistema se Redis ficar lento ou indisponível?
- Como uma hot key afeta uma implantação particionada?
Próximos conteúdos
Continue por CDN e estratégias de cache, para comparar onde cada cópia deve viver, e por replicação e sharding, para aprofundar disponibilidade e distribuição de dados. Depois, conecte o tema a rate limiting, filas e tópicos e idempotência, casos em que operações rápidas no Redis ajudam, mas não substituem um protocolo de falhas bem definido.