Redis
O Redis (REmote DIctionary Server) é a resposta padrão para "qual cache distribuído?" — e muito mais que um cache. É um servidor em memória de estruturas de dados: strings, hashes, listas, sets, sorted sets, streams e outras, cada uma com operações atômicas. A BoxOffice o usa como cache do catálogo, mas também como armazenamento de sessões, rate limiter, fila da sala de espera e contador de "pessoas vendo agora". Saber quanto custa cada estrutura, como o Redis persiste e replica, e como o Redis Cluster distribui as chaves é o que permite usá-lo bem — e reconhecer quando ele é a ferramenta errada.
O que torna o Redis rápido
- Tudo está em RAM. Uma leitura é uma consulta a uma tabela hash mais uma cópia de memória; o disco só é usado para persistência, em segundo plano.
- Uma thread executa os comandos. Os comandos rodam um de cada vez numa única thread principal (desde o Redis 6, threads de I/O separadas podem cuidar da leitura e escrita na rede). Não há locks dentro do servidor, e todo comando é atômico: nenhum outro comando roda no meio de um
INCR. - Protocolo simples, valores pequenos. A maioria dos comandos é O(1) ou O(log n). No mesmo data center, a ida e volta de um
GETpequeno custa uma fração de milissegundo, dominada pela rede.
A thread única também é a principal limitação do Redis: um comando lento (uma varredura O(n) de uma chave grande, um script Lua demorado) bloqueia todos os clientes. A vazão por nó é alta — da ordem de 100.000 comandos simples por segundo ou mais em hardware típico, um número ilustrativo que você deve verificar com o redis-benchmark no seu ambiente — e escalar além de um nó significa usar o Redis Cluster.
Redis, Valkey e as alternativas (2026)
Em 2024 o Redis trocou a licença open source BSD por licenças source-available (RSALv2/SSPLv1). Em resposta, a Linux Foundation iniciou o Valkey, um fork do Redis 7.2 apoiado por vários provedores de nuvem, que continua com licença BSD e compatível no protocolo. Em 2025, o Redis 8 acrescentou a AGPLv3, open source, como opção de licença. Neste curso — e na maioria das entrevistas — "Redis" significa o protocolo e o modelo de dados, que Redis e Valkey compartilham12.
| Redis / Valkey | Memcached | Dragonfly | |
|---|---|---|---|
| Modelo de dados | estruturas de dados ricas | só strings | compatível com Redis |
| Threads | uma thread de comandos (+ threads de I/O) | multithread | multithread, shared-nothing |
| Persistência | RDB, AOF | nenhuma | snapshots |
| Replicação / cluster | embutidos (Sentinel, Cluster) | nenhum (os clientes particionam) | replicação embutida |
| Quando | escolha padrão | um cache puro, simples e muito grande de blobs | quando um nó grande precisa substituir um cluster |
Estruturas de dados e o que a BoxOffice faz com elas
| Tipo | Comandos principais | Complexidade | Uso na BoxOffice |
|---|---|---|---|
| String | GET SET INCR EXPIRE MGET | O(1) | JSON do evento cacheado; contadores; marcadores de idempotência |
| Hash | HSET HGET HGETALL HINCRBY | O(1) por campo | sessão (session:9f1c → usuário, papéis, carrinho); campos do evento atualizados separadamente |
| List | LPUSH RPOP LRANGE | O(1) nas pontas | filas de trabalho simples; "últimos 10 eventos vistos" |
| Set | SADD SISMEMBER SMEMBERS | O(1) por membro | tags de um evento; ids já notificados |
| Sorted set | ZADD ZRANK ZRANGE ZPOPMIN | O(log n) | sala de espera: fãs ordenados pela hora de chegada; eventos mais vendidos |
| HyperLogLog | PFADD PFCOUNT | O(1), ~12 KB por contador | visitantes únicos por evento (~0,81% de erro padrão) |
| Bitmap | SETBIT BITCOUNT | O(1) por bit | usuários ativos por dia, por id de usuário |
| Geo | GEOADD GEOSEARCH | O(log n) | "eventos perto de mim" |
| Stream | XADD XREADGROUP XACK | O(1) para anexar | log de eventos leve com grupos de consumidores (o Kafka é tratado em Mensageria) |
> SET boxoffice:catalog:event:42:v3 '{"id":42,"name":"Stadium Show"}' EX 300
OK
> TTL boxoffice:catalog:event:42:v3
(integer) 300
> HSET session:9f1c user 1017 roles fan cart 3
(integer) 3
> INCR views:event:42
(integer) 1
> ZADD waitingroom:42 1760695200123 fan:1017 1760695200456 fan:2203
(integer) 2
> ZRANK waitingroom:42 fan:2203
(integer) 1
> ZPOPMIN waitingroom:42 1
1) "fan:1017"
2) "1760695200123"
> PFADD visitors:event:42 fan:1017 fan:2203 fan:1017
(integer) 1
> PFCOUNT visitors:event:42
(integer) 2
A sala de espera é um bom exemplo de escolha de estrutura: um sorted set com a hora de chegada (em milissegundos) como score dá a cada fã sua posição em O(log n) (ZRANK), e permite ao job de admissão pegar os próximos 500 fãs num só comando (ZPOPMIN waitingroom:42 500).
Idas e voltas: pipelining e comandos multichave
A 0,5 ms por ida e volta, ler 100 chaves uma a uma custa 50 ms. O MGET (ou o pipelining — enviar muitos comandos sem esperar cada resposta) faz isso em cerca de uma ida e volta. Uma página que precisa de muitos objetos cacheados deve buscá-los juntos.
Atomicidade
Como os comandos rodam um de cada vez, um comando isolado é atômico. Para mais de um comando, o Redis oferece três ferramentas:
| Ferramenta | O que oferece | Limites |
|---|---|---|
Comando único (INCR, SET ... NX, ZPOPMIN) | leitura-modificação-escrita atômica | uma chave, uma operação |
MULTI / EXEC (+ WATCH) | enfileira comandos e os executa juntos, sem intercalação; o WATCH aborta se uma chave observada mudou (lock otimista) | sem rollback: se um comando falha, os outros executam mesmo assim; não dá para usar o resultado de uma leitura dentro da transação |
Scripts Lua (EVAL) / functions (FCALL, Redis 7+) | lógica arbitrária executada atomicamente no servidor | bloqueia o servidor enquanto roda — mantenha os scripts curtos |
Um rate limiter de janela fixa para o API gateway em dois comandos:
> INCR rl:client:abc:202610171000
(integer) 1
> EXPIRE rl:client:abc:202610171000 60 NX
(integer) 1
Se o contador devolvido pelo INCR passar de 100, a requisição é rejeitada. (EXPIRE ... NX define a expiração só na primeira vez. Algoritmos mais refinados — token bucket, janela deslizante — estão em Tráfego e Borda.)
Uma pré-reserva de assento no Redis — e por que o SQL ainda decide
Durante a venda relâmpago, milhares de fãs clicam nos mesmos bons lugares. Uma pré-reserva no Redis consegue rejeitar a maioria dos perdedores em microssegundos, antes de chegarem ao banco:
> SET hold:42:A-12 fan:1017 NX PX 600000
OK
> SET hold:42:A-12 fan:2203 NX PX 600000
(nil)
O NX faz a gravação dar certo só se ninguém tiver a chave; o PX 600000 a libera automaticamente depois de 10 minutos. O segundo fã recebe nil na hora: "lugar ocupado".
É tentador parar aí. Não pare. O fã que recebeu OK ainda passa pelo serviço inventory, que faz a reserva numa transação PostgreSQL com uma restrição que torna a venda dupla impossível (Bancos de dados — o bug da venda dupla). O Redis é um filtro rápido; o banco decide. Por quê:
- A replicação do Redis é assíncrona: depois de um failover, o novo primário pode não ter a reserva que acabou de ser concedida — dois fãs poderiam receber
OK(replicação). - Pressão de memória, um
FLUSHALLpor engano ou um reinício do cache sem persistência apagam reservas silenciosamente. - O pedido, o pagamento e o ingresso vivem no SQL; o estado do assento precisa estar na mesma transação que eles.
Se o Redis perder uma pré-reserva, o pior caso é que dois fãs cheguem ao banco e um deles perca lá. A correção nunca dependeu do Redis.
Persistência
O Redis mantém os dados em memória, mas pode gravá-los em disco, para que um reinício não comece frio.
| Modo | Como funciona | Perda de dados numa queda | Reinício | Uso |
|---|---|---|---|---|
| Nenhum | só memória | tudo | frio, rápido | cache puro que pode ser reconstruído |
| RDB (snapshot) | faz fork e grava um snapshot de um instante a cada N minutos / M escritas | desde o último snapshot (minutos) | carga rápida de um arquivo compacto | caches que devem reiniciar aquecidos; backups |
| AOF (append-only file) | registra cada escrita; appendfsync everysec (padrão) ou always | ~1 s com everysec; quase nada com always (lento) | reexecuta o log (mais lento) | Redis como armazenamento principal de dados não críticos (sessões) |
| RDB + AOF | os dois | como o AOF | o AOF é usado na recuperação | a configuração recomendada quando os dados importam |
Para um cache, RDB ou nada costuma bastar. Se você se pegar ajustando appendfsync always, pergunte-se se esse dado não deveria estar num banco de dados.
Replicação e failover
Um primário Redis pode ter várias réplicas que recebem um fluxo das suas escritas. As réplicas servem leituras (um pouco defasadas) e são candidatas à promoção se o primário falhar.
flowchart LR
APP["serviço catalog"] -->|"escritas e leituras"| P[("Redis primário")]
P -->|"replicação assíncrona"| R1[("réplica 1")]
P -->|"replicação assíncrona"| R2[("réplica 2")]
S1["Sentinel 1"] -.->|"monitora"| P
S2["Sentinel 2"] -.->|"monitora"| P
S3["Sentinel 3"] -.->|"monitora"| P
S1 -.->|"quórum concorda: promover"| R1 - A replicação é assíncrona: o primário confirma uma escrita antes de as réplicas a terem. Se o primário morre, as últimas escritas confirmadas podem se perder.
WAIT numreplicas timeoutfaz o cliente esperar até N réplicas terem suas escritas — reduz a janela, mas não torna o Redis fortemente consistente. - O Redis Sentinel é um grupo de processos (pelo menos 3) que monitoram o primário; quando um quórum concorda que ele caiu, promovem uma réplica e informam o novo endereço aos clientes. O failover leva segundos — enquanto isso o cache fica indisponível ou só de leitura.
min-replicas-to-writefaz o primário recusar escritas quando não alcança réplicas suficientes, limitando a perda durante uma partição.
Para um cache isso é aceitável: uma escrita perdida é uma falta no futuro. Para dados que não podem ser perdidos, é o motivo para manter a fonte da verdade num banco de dados.
Redis Cluster
Quando um nó não basta — em memória ou em vazão — o Redis Cluster particiona o espaço de chaves entre vários primários, cada um com suas réplicas3.
- O espaço de chaves é dividido em 16.384 hash slots. O slot de uma chave é
CRC16(key) mod 16384. - Cada primário é dono de um conjunto de slots (por exemplo, três primários: 0–5460, 5461–10922, 10923–16383).
- Os clientes são cluster-aware: guardam o mapa slot → nó e mandam cada comando direto ao nó certo. Se o mapa estiver desatualizado, o nó responde
MOVED <slot> <endereço>e o cliente atualiza o mapa; durante a migração de um slot, oASKredireciona uma única requisição. - Acrescentar um nó = mover alguns slots para ele, com o sistema no ar, sem refazer o hash de todas as chaves.
flowchart LR
C["cliente cluster-aware<br/>mapa de slots em cache"] -->|"event:42 → slot 16271"| N3
C -->|"prices:42 → slot 9049"| N2
N1[("primário A<br/>slots 0–5460")] --- R1[("réplica A")]
N2[("primário B<br/>slots 5461–10922")] --- R2[("réplica B")]
N3[("primário C<br/>slots 10923–16383")] --- R3[("réplica C")] > CLUSTER KEYSLOT event:42
(integer) 16271
> CLUSTER KEYSLOT prices:42
(integer) 9049
> CLUSTER KEYSLOT {event:42}:seats
(integer) 16271
> CLUSTER KEYSLOT {event:42}:prices
(integer) 16271
Operações multichave (MGET, MULTI/EXEC, scripts Lua que tocam várias chaves) só funcionam quando todas as chaves estão no mesmo slot — caso contrário o Redis responde CROSSSLOT. As hash tags resolvem isso: se uma chave contém {...}, só a parte entre as chaves é usada no hash. {event:42}:seats e {event:42}:prices caem no mesmo slot, então um script pode atualizar as duas atomicamente.
Hash tags concentram carga
Tudo com a mesma tag mora num nó só. Use como tag algo com muitos valores (um evento, um usuário), nunca algo com poucos ({boxoffice} colocaria a aplicação inteira num nó — uma chave quente feita por você mesmo).
Hash slots vs. hashing consistente
O Memcached segue outro caminho: os servidores não se conhecem, e o cliente decide qual servidor guarda uma chave, normalmente com hashing consistente (o algoritmo ketama), de modo que acrescentar um servidor remapeie só ~1/N das chaves (Bancos de dados — hashing consistente). O Redis Cluster usa um número fixo de slots: o mapeamento chave → slot nunca muda, só o slot → nó, e os próprios nós coordenam o failover e os redirecionamentos.
| Redis Cluster (hash slots) | Hashing consistente no cliente (Memcached) | |
|---|---|---|
| Quem conhece o mapeamento | servidores + clientes cluster-aware | só os clientes |
| Acrescentar um nó | mover alguns slots, no ar | ~1/N das chaves são remapeadas (viram faltas) |
| Replicação / failover | embutidos | nenhum (um nó morto = faltas) |
| Operações multichave | dentro de um slot (hash tags) | nenhuma |
Redis gerenciado
- Amazon ElastiCache (Redis OSS, Valkey, Memcached): cache gerenciado com replicação, failover automático, modo Cluster e uma opção serverless.
- Amazon MemoryDB: compatível com Redis/Valkey, com um log de transações durável e multi-AZ — pensado para ser um banco de dados principal, não só um cache.
- Memorystore for Redis, Memorystore for Redis Cluster e Memorystore for Valkey: instâncias gerenciadas com réplicas e failover automático.
- Memorystore for Memcached.
- Azure Managed Redis (baseado no Redis Enterprise) e os níveis mais antigos do Azure Cache for Redis, com opções de replicação, cluster e geo-replicação.
- Redis ou Valkey auto-hospedados: um único nó com Docker para desenvolvimento (Hands-on 5); Sentinel ou Cluster em VMs; no Kubernetes com um operator ou um Helm chart (StatefulSet + volumes persistentes).
- Memcached e Dragonfly como alternativas.
Serviços gerenciados cuidam de failover, atualizações e backups; continuam sendo seus o dimensionamento, a política de despejo, o desenho das chaves, os timeouts e o que acontece quando ele falha.
Dimensionando uma implantação Redis
Exemplo resolvido — o cluster de cache da BoxOffice
Premissas ilustrativas: 2 milhões de chaves cacheadas (eventos, preços, resultados de busca, fragmentos); chave média de 50 bytes; valor médio de 2 KB; custo por chave (entrada do dicionário, cabeçalhos de objeto, expiração) ~100 bytes.
- Dados: 2.000.000 × (50 + 2.048 + 100) B ≈ 4,4 GB.
- Folga para fragmentação e crescimento (+30%): ≈ 5,7 GB.
- Folga para o fork dos snapshots RDB num primário com muitas escritas: manter o uso de memória ≤ ~70% da RAM → ≈ 8 GB por cópia.
- Vazão: 300.000 leituras/s no pico depois dos caches L1; supondo (a medir em benchmark) ~100.000 ops/s por primário → 3 primários, cada um com 1 réplica.
- Resultado: Redis Cluster com 3 primários × ~1,5 GB de dados cada (≈ 2,7 GB de RAM com a folga acima) + 3 réplicas — 6 nós com 4–8 GB de RAM. A restrição é a vazão, não a memória.
As sessões (2 M fãs × 0,5 KB ≈ 1 GB) vão para uma instância pequena separada, com noeviction e AOF, para que despejos do cache nunca desconectem um fã.
Conectando a partir do Spring Boot
A configuração usada no Hands-on 5:
spring:
data:
redis:
host: ${REDIS_HOST:redis}
port: 6379
timeout: 50ms # command timeout: fail fast if Redis is slow
connect-timeout: 200ms
# cluster:
# nodes: redis-0:6379,redis-1:6379,redis-2:6379
cache:
type: redis
redis:
time-to-live: 5m # default TTL for @Cacheable caches
cache-null-values: false
key-prefix: "boxoffice:catalog:"
services:
redis:
image: redis:8-alpine
command: ["redis-server", "--maxmemory", "256mb", "--maxmemory-policy", "allkeys-lru"]
ports:
- "6379:6379"
Na entrevista
Quando você disser "Redis", prepare-se para três perguntas seguintes. Modelo de dados: escolha a estrutura que combina com o padrão de acesso ("um sorted set para a sala de espera, um hash para as sessões"). Escala: Redis Cluster com 16.384 hash slots, hash tags para operações multichave e réplicas para failover — e por que uma chave quente não se resolve acrescentando nós. Durabilidade: a replicação é assíncrona, então o Redis é um cache ou um armazenamento para dados que você pode perder; a reserva do assento e o pagamento vivem no banco.
Boas práticas
- Separe um cache de um armazenamento: um cache puro roda com
maxmemory, uma política de despejoallkeys-*e sem persistência; sessões ou filas precisam de uma instância própria comnoevictione persistência. No BoxOffice: o Redis do lab 5 é um cache puro (--save "" --appendonly no). - Escolha a estrutura de dados que responde à pergunta num único comando (um hash para um objeto, um sorted set para um ranking,
INCRpara um contador) em vez de ler-modificar-escrever na aplicação. - Agrupe as idas e voltas (
MGET, pipelines) quando uma página precisa de muitas chaves: o custo é a rede, não o Redis. - Use
SCAN,UNLINKe réplicas na manutenção, nuncaKEYS *ou umDELbloqueante no primário. - Planeje a memória com folga (conjunto de trabalho × 1,5) e alerte em memória, despejos e atraso de replicação.
Armadilhas comuns
KEYS *em produção — O(n) sobre todo o espaço de chaves, bloqueia o servidor. UseSCAN.- Chaves sem TTL num cache — a memória cresce até começarem os despejos ou os erros de
OOM. - Valores grandes e coleções enormes — veja chaves grandes.
- Redis como única cópia de dados de dinheiro — pedidos, pagamentos e reservas de assento pertencem ao banco.
- Timeouts padrão — um Redis lento ou falhando não pode travar o serviço.
- Uma instância para cache e sessões — a política de despejo do cache vai despejar sessões.
- Hash tags com baixa cardinalidade — a aplicação inteira num nó só.
Como se conecta
flowchart LR
FAST["em memória,<br/>um comando por vez"] --> DS["estruturas de dados: strings,<br/>hashes, sorted sets, streams"]
DS --> WR["sorted set =<br/>sala de espera → Mensageria"]
DS --> SESS["hashes = sessões,<br/>strings = JSON cacheado"]
FAST --> ATOM["atomicidade: comandos únicos,<br/>MULTI, Lua"]
ATOM --> RL["contadores de rate limit<br/>→ API gateway da borda"]
ATOM --> PRE["pré-reserva no Redis,<br/>o SQL ainda decide → Bancos de dados"]
FAST --> PERS["persistência: RDB, AOF"]
FAST --> REP["replicação assíncrona,<br/>failover"]
REP --> CL["Redis Cluster<br/>16.384 hash slots"]
CL -.-> CH["vs hashing consistente<br/>→ Particionamento"] Componentes da paleta: cache (Redis ou Valkey, gerenciado ou próprio) · queue (uma sala de espera em sorted set; listas como filas de jobs simples) · stream (Redis Streams, um log de eventos leve) · apigw (contadores de rate limit com INCR + EXPIRE) · sql (ainda decide a reserva do assento) · app (clientes Spring Boot com pools e timeouts). Cenários: read-heavy-cache, flash-sale, rate-limiter. Continua em: Bancos de dados — hashing consistente, Bancos de dados — replicação, Borda — token bucket, Estudo de caso: cache distribuído, Hands-on 5.
-
REDIS. Documentação do Redis. ↩
-
REDIS. Redis cluster specification. ↩