O que é Redis?
Redis é um servidor de estruturas de dados em memória. Ele mantém todo o seu conjunto de dados na RAM e responde a comandos em microssegundos. Cada valor é armazenado sob uma chave de string, e o valor pode ser uma string, um hash, uma lista, um set, um sorted set, um stream, um bitmap ou um HyperLogLog.
Surgiu em 2009 como uma forma de acelerar um produto de analytics em tempo real e rapidamente se tornou o cache padrão para a web. O motivo não é apenas a velocidade. O Redis também traz um conjunto de comandos pequeno e composível, estruturas de dados úteis, TTLs, replicação e scripting — o suficiente para que as equipes o utilizem para muito mais do que apenas caching.
Pense no Redis como uma estrutura de dados em memória compartilhada que todos os processos podem visualizar. Essa perspectiva explica tanto seus pontos fortes quanto suas limitações.
Por que o Redis é rápido
Três fatores tornam o Redis rápido, e nenhum deles é segredo.
Primeiro, os dados residem na memória. Não há busca em disco nem pool de buffer para gerenciar no caminho de leitura. O acesso à memória é medido em nanossegundos; uma viagem de ida e volta na rede (round trip) é medida em frações de milissegundo.
Segundo, não existe um planejador de consultas (query planner). Um comando como GET user:42 é uma busca em hash-table, e ZADD é uma inserção em skip-list. Não há parsing de linguagem de consulta, etapa de otimização nem joins. A operação é fixa e direta.
Terceiro, os comandos são executados um por vez em uma única thread. Isso pode parecer uma fraqueza, mas elimina a contenção de locks e torna cada comando atômico. O event loop gerencia milhares de conexões e executa os comandos sequencialmente, e é por isso que uma única instância consegue entregar um throughput enorme.
O problema é que um único comando lento bloqueia tudo. KEYS * em milhões de chaves, ou ZRANGE em um sorted set gigante, trava todos os outros clientes. Mantenha os comandos limitados e use SCAN em vez de KEYS em produção.
As estruturas de dados que importam
Escolher a estrutura certa é a maior parte da habilidade. Aqui está a finalidade de cada uma.
Strings armazenam texto, JSON, contadores e dados binários. SET, GET, INCR, APPEND e SETEX são as ferramentas fundamentais.
SET user:42:name "Ada"
INCR page:home:views
SETEX token:abc123 900 "opaque-token-value"
Hashes armazenam um objeto como pares de campo-valor sob uma única chave. São perfeitos para sessões e registros que você atualiza campo por campo, pois evitam a serialização de todo o objeto a cada gravação.
HSET session:9f2c userId 42 role admin
HINCRBY session:9f2c pageViews 1
HGET session:9f2c role
Lists são sequências ordenadas com push e pop O(1) em ambas as extremidades. São ideais para filas simples, feeds de atividades recentes e logs limitados.
LPUSH queue:emails "welcome:42"
RPOP queue:emails
LTRIM feed:global 0 99
Sets armazenam membros únicos e não ordenados. Use-os para tags, seguidores e testes de associação.
SADD post:7:tags redis database
SISMEMBER post:7:tags redis
SINTER user:1:follows user:2:follows
Sorted sets são o destaque. Cada membro possui um score, portanto, o conjunto permanece ordenado por score enquanto as buscas mantêm-se em O(log n). Leaderboards, filas de prioridade e janelas de rate-limit utilizam essa estrutura.
ZADD leaderboard 1500 ada
ZINCRBY leaderboard 50 ada
ZREVRANGE leaderboard 0 9 WITHSCORES
Streams são logs de apenas anexação (append-only) com grupos de consumidores, mais próximos do Kafka do que de uma lista. Use-os quando precisar de replay, confirmação (acknowledgement) e múltiplos consumidores.
XADD events * type signup userId 42
XREADGROUP GROUP workers alice COUNT 10 STREAMS events ">"
Bitmaps e HyperLogLogs lidam com contagens especializadas. Bitmaps rastreiam booleanos por usuário usando um bit cada, ideal para usuários ativos diários; HyperLogLog estima contagens únicas com cerca de 12 KB e uma pequena taxa de erro.
SETBIT active:2026-09-16 42 1
PFADD visitors:2026-09-16 user:42
PFCOUNT visitors:2026-09-16
Se você for lembrar de apenas uma regra, que seja esta: escolha a estrutura que corresponda ao padrão de acesso, não a que corresponda ao JSON que você já possui.
Nomeação de chaves e namespacing
O Redis não possui tabelas, schemas nem namespaces. A única estrutura é a própria chave, portanto, a convenção de nomenclatura é o seu schema.
Um padrão comum é object:id:attribute em segmentos separados por dois-pontos:
user:42
user:42:sessions
session:9f2c
order:1001:items
rate:203.0.113.7:2026091612
Chaves curtas e previsíveis reduzem o consumo de memória e facilitam a varredura e a depuração. Adicione um prefixo de versão ou ambiente quando várias aplicações compartilharem a mesma instância: app:v2:user:42. Evite chaves derivadas de inputs de usuário sem validação e nunca utilize espaços ou quebras de linha.
Como as chaves são strings, você pode listá-las para depuração, mas faça isso com cuidado. SCAN com um cursor é seguro; KEYS não é, pois bloqueia o servidor enquanto percorre todo o keyspace.
Expiração, TTL e eviction
A expiração é o que torna o Redis um cache em vez de um vazamento de memória. Quase toda chave que você criar para cache deve ter um TTL.
SET user:42 '{"name":"Ada"}' EX 300 # seconds
SET user:42 '{"name":"Ada"}' PX 300000 # milliseconds
EXPIRE user:42 300
TTL user:42
PERSIST user:42
EXPIRE e suas variantes definem um timeout; TTL reporta os segundos restantes. A expiração é lazy e ativa: as chaves são removidas quando acessadas após o seu tempo, e um ciclo em background amostra e limpa chaves expiradas para que a memória seja recuperada mesmo que elas nunca sejam lidas novamente.
Apenas TTLs não são suficientes quando o conjunto de dados cresce mais rápido do que expira. Configure maxmemory e uma maxmemory-policy:
noeviction— rejeita escritas quando estiver cheio; o padrão seguro para dados duráveis.allkeys-lru— remove a chave menos recentemente utilizada de qualquer tipo; a escolha comum para caches.allkeys-lfu— remove a chave menos frequentemente utilizada, melhor quando um pequeno conjunto de dados “quentes” é prioritário.volatile-lru/volatile-ttl— remove apenas chaves que possuem expiração, protegendo assim as chaves persistentes.
CONFIG SET maxmemory 2gb
CONFIG SET maxmemory-policy allkeys-lru
A escolha é importante. allkeys-lru removerá tranquilamente uma sessão se ela for a chave mais “fria”; volatile-ttl não o fará, porque sessões sempre possuem expiração.
Padrões de cache e invalidação
O padrão mais comum é o cache-aside. A aplicação verifica primeiro o Redis, recorre ao banco de dados em caso de miss e grava o resultado de volta com um TTL.
async function getUser(id) {
const key = `user:${id}`;
const cached = await redis.get(key);
if (cached) return JSON.parse(cached);
const user = await db.users.findById(id);
if (user) await redis.set(key, JSON.stringify(user), { EX: 300 });
return user;
}
Outros dois padrões valem a pena conhecer. O Write-through atualiza o cache e o banco de dados simultaneamente, o que mantém as leituras “quentes”, mas dobra o caminho de escrita. O Write-behind grava primeiro no Redis e descarrega no banco de dados de forma assíncrona, o que é rápido, mas traz o risco de perda de escritas.
A invalidação é a parte difícil. Um TTL garante a correção eventual; deleções explícitas tornam a atualização imediata. O hábito mais confiável é atualizar o banco de dados primeiro e, em seguida, deletar a chave do cache, aceitando uma breve janela onde um leitor concorrente pode repopular dados obsoletos.
async function updateUser(id, patch) {
const user = await db.users.update(id, patch);
await redis.del(`user:${id}`);
return user;
}
Evite a tentação de manter um cache para sempre só porque a invalidação é incômoda. Dados obsoletos e memória sem limite são problemas piores do que uma taxa de hit ligeiramente menor.
Sessões, rate limiting e leaderboards
O Redis brilha sempre que o estado precisa ser compartilhado entre processos e sobreviver a reinicializações.
Sessões são um hash ou uma string serializada com um TTL que desliza a cada requisição.
await redis.set(`session:${sid}`, JSON.stringify(data), { EX: 3600 });
await redis.expire(`session:${sid}`, 3600); // sliding window
Rate limiting é um contador com expiração. Uma janela fixa (fixed window) utiliza dois comandos; já uma janela deslizante (sliding window) utiliza um sorted set de timestamps.
const minute = Math.floor(Date.now() / 60_000);
const key = `rate:${ip}:${minute}`;
const replies = await redis.multi().incr(key).expire(key, 60).exec();
if (replies[0] > 100) throw new Error("rate_limited");
Leaderboards são um sorted set. ZADD registra uma pontuação, ZINCRBY a ajusta e ZREVRANGE retorna os melhores jogadores com suas pontuações. ZRANK fornece a posição de um único jogador, que é exatamente o que uma página de perfil precisa.
ZADD leaderboard 1500 ada
ZINCRBY leaderboard 50 ada
ZREVRANGE leaderboard 0 9 WITHSCORES
ZRANK leaderboard ada
Todos os três padrões dependem das mesmas duas propriedades: os comandos são atômicos e cada chave pode expirar.
Filas e pub/sub
Listas formam uma fila de trabalho confiável. Produtores LPUSH, workers BRPOP — o pop bloqueante significa que um worker dorme até que um job chegue, em vez de fazer polling.
LPUSH queue:emails "welcome:42"
BRPOP queue:emails 30
Para uma fila confiável, mova os jobs para uma lista de processamento antes de manipulá-los e remova-os apenas em caso de sucesso. Dessa forma, um worker que sofra um crash não perde um job silenciosamente.
Pub/sub é a mensageria do tipo “dispare e esqueça”: publishers PUBLISH, subscribers recebem em canais. É excelente para notificações em tempo real e sinais de cache-busting, mas não há persistência nem garantia de entrega. Se um subscriber estiver offline, a mensagem é perdida.
PUBLISH notifications:user:42 "You have a new follower"
SUBSCRIBE notifications:user:42
Quando você precisar de persistência, confirmação (acknowledgement) e replay, use streams com consumer groups. Streams são a resposta moderna para “eu quero pub/sub, mas de forma confiável”.
Pipelines, transações e Lua
Um comando representa uma ida e volta (round trip) na rede. Se você precisar de dez valores, dez GETs sequenciais gastarão a maior parte do tempo esperando pela rede. Um pipeline envia todos eles juntos e lê todas as respostas de uma só vez.
const [name, plan, logins] = await redis
.multi()
.hget("user:42", "name")
.hget("user:42", "plan")
.hget("user:42", "logins")
.exec();
Um pipeline não é automaticamente uma transação. Envolver comandos em MULTI/EXEC faz com que eles sejam executados como um bloco atômico, sem que comandos de outros clientes sejam intercalados. O Redis não realiza rollback em caso de erro de comando da mesma forma que o SQL; ele simplesmente continua, portanto, valide as entradas antes de enfileirá-las.
Para lógicas que devem ser executadas atomicamente no servidor, use um script Lua. Ele é executado como um único comando, pode ler e escrever várias chaves e é a ferramenta correta para operações de compare-and-set, como liberar um lock apenas se você ainda for o proprietário dele.
const release = `
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
end
return 0
`;
await redis.eval(release, { keys: ["lock:order:1001"], arguments: [token] });
Scripts devem ser pequenos e determinísticos. Não execute nada sem limite (unbounded) dentro de um script, pois o servidor inteiro ficará aguardando a sua conclusão.
Persistência: RDB e AOF
In-memory não significa necessariamente efêmero, mas a durabilidade é um trade-off que você escolhe.
RDB grava snapshots do conjunto de dados em disco em um ponto específico no tempo, seja via agendamento ou sob demanda. Snapshots são compactos, restauram rapidamente e são ideais para backups. O custo é que, em caso de crash, perde-se tudo o que foi gravado desde o último snapshot.
SAVE # blocking snapshot, avoid in production
BGSAVE # fork and snapshot in the background
AOF anexa cada comando de escrita a um log. Com appendfsync everysec você perde no máximo cerca de um segundo de escritas; com always você quase não perde nada, mas paga um custo de escrita muito mais alto. Arquivos AOF podem ser reescritos em background para permanecerem compactos.
CONFIG SET appendonly yes
CONFIG SET appendfsync everysec
Muitas implantações habilitam ambos: RDB para restaurações rápidas e AOF para uma janela de perda reduzida. Se você usa Redis puramente como cache, pode desativar a persistência completamente e deixar que um cold cache seja preenchido a partir do banco de dados. Se você o utiliza para filas ou contadores importantes, mantenha a persistência ativada e entenda exatamente quanta perda de dados um crash pode causar.
Replicação, Sentinel e Cluster
Uma replica se conecta a um primário e recebe um fluxo de escritas, mantendo assim uma cópia em tempo quase real. As replicas podem processar leituras, o que reduz a carga do primário, e servem como base para o failover.
O Sentinel monitora um primário e suas replicas, promovendo uma replica quando o primário falha. Ele também informa aos clientes o endereço do novo primário. O Sentinel proporciona alta disponibilidade para um conjunto de dados que caiba em um único nó.
O Redis Cluster distribui os dados entre nós utilizando 16.384 hash slots. Cada chave é mapeada para um slot através do hash de seu nome, e cada nó é dono de um intervalo de slots. Os clientes podem se comunicar com qualquer nó e são redirecionados para o correto. O Cluster oferece escalabilidade horizontal e failover, ao custo de maior complexidade: operações com múltiplas chaves devem manter as chaves no mesmo slot, geralmente através de hash tags como user:{42}:name.
O caminho comum é começar com uma única instância, depois adicionar uma replica, seguir para o Sentinel e, por fim, utilizar o Cluster apenas quando a memória não couber mais em uma única máquina.
Anti-padrões comuns
- Redis como único banco de dados. Sem persistência e replicação configuradas para durabilidade, uma falha pode causar perda de dados. Mantenha um sistema de registro durável.
- Chaves sem limite. Toda chave em cache precisa de um TTL, caso contrário, a memória enche e a expulsão (eviction) começa a descartar dados necessários.
- Chaves e coleções gigantes. Um único hash com milhões de campos, ou uma lista com milhões de entradas, é lento para deletar e pode bloquear o servidor.
KEYSem produção. Isso bloqueia o event loop. UseSCAN.- Uma única chave para tudo. Serializar um objeto inteiro a cada alteração causa amplificação de escrita. Use hashes e atualize campos específicos.
- Ignorar a política de expulsão.
allkeys-lrupode expulsar sessões e locks, não apenas entradas de cache.
Conectando a partir do Node
Dois clientes dominam o ecossistema Node: node-redis e ioredis. Ambos utilizam o mesmo protocolo e expõem um método por comando, então a escolha geralmente se resume à preferência pela API e ao suporte a clustering.
import { createClient } from "redis";
const redis = createClient({ url: process.env.REDIS_URL });
redis.on("error", (err) => console.error("redis", err));
await redis.connect();
await redis.set("health", "ok", { EX: 60 });
const value = await redis.get("health");
Crie o cliente apenas uma vez na inicialização e compartilhe-o. Uma conexão é um socket com uma fila de comandos, e abrir uma por requisição adiciona latência e consome descritores de arquivo. Sempre anexe um handler de error: sem ele, uma conexão interrompida pode surgir como uma exceção não tratada.
A maioria dos comandos retorna valores simples, mas as opções são passadas como um objeto. SET aceita { EX: 300 } para segundos, { NX: true } para escrever apenas se a chave estiver ausente, e { KEEPTTL: true } para preservar a expiração existente. NX somado a um TTL é a receita padrão para um lock distribuído.
const acquired = await redis.set(`lock:order:${id}`, token, {
NX: true,
EX: 30,
});
Para um deployment sharded, escolha um cliente que entenda redirecionamentos de cluster e hash tags. Ambos os principais clientes suportam isso; a parte importante é manter chaves relacionadas no mesmo slot quando um comando toca em mais de uma.
Monitorando uma instância em execução
Alguns poucos comandos revelam quase tudo sobre a saúde de uma instância Redis.
INFO memory
INFO stats
DBSIZE
SLOWLOG GET 10
INFO memory reporta used_memory, maxmemory e a política atual. INFO stats inclui keyspace_hits e keyspace_misses, cuja proporção é a sua taxa de acerto de cache (cache hit rate) — o número a ser observado ao ajustar os TTLs. DBSIZE conta as chaves, e SLOWLOG registra comandos que excederam um limite de latência.
CONFIG SET slowlog-log-slower-than 10000 # microseconds
CONFIG RESETSTAT
Mais duas ferramentas são úteis, porém perigosas. MONITOR transmite cada comando que o servidor processa em tempo real; é inestimável para debugging, mas caro demais para ser deixado ativado em produção. SCAN percorre o keyspace em lotes do tamanho do cursor e é seguro, ao contrário de KEYS.
Monitore três números em um dashboard: o uso de memória em relação a maxmemory, a contagem de evicções e a taxa de acerto. Uma taxa de acerto em queda com evicções em alta geralmente significa que os TTLs estão curtos demais ou que o conjunto de dados não cabe mais na memória; nesses casos, é melhor adicionar memória ou corrigir as chaves do que aumentar o limite e permitir que a instância faça swap.
Melhores práticas
- Defina um TTL para cada chave em cache e escolha uma política de expulsão (eviction policy) que seja adequada aos dados.
- Use um esquema de nomenclatura de
object:id:attributeconsistente e documente-o. - Escolha a estrutura de dados que melhor se adapte ao padrão de acesso antes de escrever o código.
- Reutilize uma única conexão de cliente e utilize pipeline para comandos independentes.
- Use Lua ou
MULTIpara operações multi-etapas atômicas e mantenha-as curtas. - Prefira
SCANem vez deKEYSe limite o tamanho de qualquer chave individual. - Ative a persistência e a replicação para dados que não podem ser reconstruídos, e teste a restauração.
- Monitore a memória, a taxa de acerto (hit rate), as expulsões e os comandos lentos antes que se tornem incidentes.
Erros comuns
- Fazer cache sem um TTL e esgotar a memória lentamente.
- Usar
KEYS *para buscar chaves e bloquear todos os outros clientes. - Tratar pub/sub como uma fila confiável e perder mensagens quando um subscriber está offline.
- Armazenar um objeto grande como uma única string JSON e reescrevê-lo a cada pequena alteração.
- Assumir que
MULTIfaz rollback como uma transação SQL; isso não acontece. - Permitir que uma hot key ou um sorted set gigante se torne um gargalo.
- Esquecer que
allkeys-lrupode remover sessões, locks e contadores de rate-limit. - Executar Redis com a persistência desativada e sem réplica, resultando em perda de dados ao reiniciar.
Próximos passos
O Redis é a face prática do caching, e entendê-lo torna concretos os padrões de caching do backend roadmap. Combine-o com um sistema de registro durável: PostgreSQL para dados relacionais ou MongoDB para documentos. Depois, conecte-o a um serviço Node e revise os Node.js basics para entender como o cliente se encaixa no fluxo da sua requisição.