Uma query que leva 200ms no seu banco de dados ainda leva 200ms na milionésima vez que alguém a executa, e cada uma dessas execuções disputa as mesmas conexões, o mesmo I/O de disco e a mesma CPU. O Redis mantém a resposta em memória, num servidor que não faz mais nada, então a milionésima leitura custa uma ida e volta de rede em vez de um plano de query.
O que o Redis realmente faz como cache
O Redis é um key-value store em memória. Os valores vivem na RAM, então leituras e escritas se medem em tempos abaixo do milissegundo em vez das dezenas ou centenas de milissegundos que uma query relacional custa assim que entram em cena joins, índices sob pressão ou um connection pool ocupado. Ele fica ao lado do seu banco de dados, não no lugar dele: sua aplicação pergunta primeiro ao Redis e só vai ao banco quando o Redis não tem a resposta.
O padrão para isso se chama cache-aside (também “lazy loading”), e é o primeiro que a maioria das aplicações usa:
- A aplicação pede uma chave ao Redis.
- Cache hit — o Redis tem, retorna. O banco nem chega a ver a requisição.
- Cache miss — o Redis não tem. Consulta o banco, escreve o resultado no Redis e então retorna.
| |
O banco continua sendo a fonte da verdade. O Redis mantém uma cópia rápida e descartável das partes que você lê com frequência. Se o Redis reiniciar vazio, nada se perde: ele se repopula sozinho no próximo lote de cache misses.
Como executar o Redis no Docker
A forma mais rápida de subir uma instância do Redis é usar a imagem oficial. Se você nunca usou Docker, O Que é Docker e Para Que Serve cobre o básico antes de continuar.
| |
Isso inicia o Redis 8 em segundo plano, publicando a porta 6379 na sua máquina. Conecte-se com o redis-cli, instalado localmente ou executado a partir de um segundo container na mesma rede:
| |
| |
TTL retornando -1 significa que a chave não tem expiração: ela fica até você apagá-la ou o Redis removê-la sob pressão de memória. Para um cache, você quase sempre quer uma expiração, para que dados obsoletos saiam de circulação sozinhos.
| |
EX 300 faz a chave expirar em 300 segundos. PX faz o mesmo em milissegundos, e EXPIRE user:42:name 300 define um TTL numa chave que já existe.
Para qualquer coisa além de uma checagem manual rápida, rode o Redis com o Compose junto do resto da sua stack — veja O Que É Docker Compose e Como Usar:
| |
A api alcança o Redis pelo hostname redis porque o Compose coloca os dois serviços na mesma rede definida pelo usuário, o mecanismo explicado em O Que É uma Rede Docker e Como Usar. O volume redis-data importa menos para um cache puro do que para um banco de dados primário (veja O Que é um Volume Docker e Como Usar): perder o cache só significa que as próximas leituras viram cache misses. Ainda assim, mantenha-o se um cache já aquecido depois de um restart importar para o seu orçamento de latência.
Como escrever a lógica cache-aside na sua aplicação
O Redis te dá os comandos; sua aplicação ainda precisa chamá-los na ordem certa. Envolver uma leitura do banco em cache-aside fica assim:
| |
Dois detalhes fazem a maior parte do trabalho aqui:
- O TTL (
ex=300) não é opcional. Sem ele, uma linha que muda no banco continua servindo seu valor antigo pelo Redis para sempre. Ajuste o TTL de acordo com quanto tempo um dado desatualizado é aceitável para aquela chave específica, não um número global para a aplicação inteira. json.dumps/json.loads. O Redis guarda strings, mais um punhado de tipos mais ricos como hashes e sorted sets — nunca os objetos da sua linguagem. Qualquer coisa estruturada precisa ser serializada na entrada e parseada na saída.
Quando a linha correspondente muda, apague a chave em vez de esperar o TTL:
| |
A próxima leitura depois de um update é um cache miss garantido, que repopula o Redis com a linha atualizada. O cache-aside nunca tenta manter o cache sincronizado em tempo real. Ele faz entradas obsoletas sumirem rápido, por TTL ou por remoção explícita na escrita.
Eviction: o que acontece quando o Redis fica sem memória
O Redis mantém tudo na RAM, então a memória é finita de um jeito que o disco geralmente não é. Defina um teto rígido com maxmemory, e diga ao Redis o que fazer ao atingir esse teto com maxmemory-policy:
| Política | Comportamento | Use para |
|---|---|---|
noeviction | Recusa novas escritas ao ficar cheio, retorna erro | Um datastore que não pode perder dados silenciosamente |
allkeys-lru | Remove a chave menos usada recentemente, qualquer chave | Um cache puro — a escolha padrão sensata |
volatile-lru | Remove a chave menos usada recentemente entre as que têm TTL | Misturar chaves de cache e chaves permanentes numa instância |
volatile-ttl | Remove primeiro a chave com o TTL restante mais curto | Rate limiters, tokens de curta duração |
Para uma instância de cache dedicada, allkeys-lru quase sempre é a certa: toda chave nela é descartável por definição, então deixe o Redis descartar o que você tocou menos recentemente. noeviction é a política errada para um cache, porque transforma “o Redis está cheio” em erros de aplicação em vez de descartar silenciosamente as entradas frias.
| |
Defina os dois no comando de inicialização, como no arquivo Compose acima, não só em runtime. Um restart do container reseta um CONFIG SET definido só em runtime para os padrões do Redis (maxmemory 0, ou seja, ilimitado, limitado só pelo host).
Quando o cache Redis é a escolha errada
O cache esconde um caminho lento; não conserta um. Quatro situações em que recorrer ao Redis primeiro piora as coisas:
- Os dados mudam a cada leitura de qualquer forma. Um ticker de bolsa ao vivo ou um contador incrementado a cada requisição não ganha nada com um cache invalidado tão rápido quanto é preenchido. Você adicionou um salto de rede sem ganhar hit rate.
- Você precisa de consistência forte. O cache-aside é eventually consistent por design: entre uma escrita e o próximo cache miss existe uma janela em que um valor obsoleto ainda pode ser servido. Para um saldo de conta ou um nível de estoque em que essa janela causa dano real, não coloque em cache, ou coloque com um TTL de segundos e aceite a troca explicitamente.
- O gargalo real está em outro lugar. Se o seu banco está lento por causa de um índice faltando ou de uma query N+1, resolva isso primeiro. Colocar em cache uma query mal escrita só move a mesma resposta errada para a memória mais rápido.
- Um thundering herd na expiração. Quando uma chave quente expira, todas as requisições concorrentes podem errar o cache ao mesmo tempo e martelar o banco simultaneamente. Use
SET key value NX EX 30como um lock de curta duração, mantido por quem estiver repopulando o cache, para que os demais esperem por essa em vez de se acumularem no banco.
Por onde começar com o cache Redis
Coloque um container redis:8 na frente da sua leitura mais lenta e mais repetida. Envolva-a em cache-aside com um TTL que você consiga justificar, não um número redondo escolhido ao acaso, e defina maxmemory-policy allkeys-lru para que um cache cheio degrade em vez de gerar erros. Depois meça o hit rate. Write-through caching, invalidação via pub/sub e Redis Streams custam complexidade, e nenhum deles vale a pena antes que um setup simples de cache-aside tenha mostrado exatamente onde ele para de dar conta.