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ãoBenefícioCusto ou risco
TTL mais longoMais hits e menos carga na fonteDados podem ficar antigos por mais tempo
Eviction automáticaUso de memória permanece limitadoUma chave útil pode desaparecer antes do TTL
AOF com sincronização frequenteMenor janela de perdaMais I/O e possível impacto de latência
Réplicas assíncronasAlta disponibilidade com pouca espera na escritaFailover pode perder escritas ainda não replicadas
Cluster com shardsMais capacidade de memória e throughputOperaçõ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

  1. Qual é a diferença entre expiração por TTL e eviction por falta de memória?
  2. Como funciona cache-aside e onde existe uma janela de inconsistência?
  3. O que é cache stampede e como você o mitigaria?
  4. Quando escolher RDB, AOF, ambos ou nenhuma persistência?
  5. Por que uma réplica não garante que toda escrita confirmada sobreviverá a um failover?
  6. Como escolher entre string, hash, set e sorted set para um requisito?
  7. O que acontece com o sistema se Redis ficar lento ou indisponível?
  8. 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.

Referências oficiais