O que é Redis?
Redis (Remote Dictionary Server) é um banco de dados in-memory de código aberto que armazena dados como estruturas de dados chave-valor. É incrivelmente rápido (operações em microssegundos) e serve como cache, message broker, session store, leaderboard e muito mais.
Características principais
- In-memory: todos os dados ficam na RAM (microsegundos de latência)
- Single-threaded para comandos (sem race conditions)
- Persistência opcional: RDB (snapshots) e/ou AOF (append-only file)
- Estruturas de dados ricas: Strings, Hashes, Lists, Sets, Sorted Sets, Streams, HyperLogLog, Bitmaps, Geo
- Replicação: master-replica out of the box
- Clustering: sharding automático desde v3.0
Quando NÃO usar Redis
- Dados que não cabem na RAM (use PostgreSQL/MongoDB)
- Quando persistência total é crítica sem estratégia de backup
- Queries complexas com JOINs (use banco relacional)
- Documentos grandes e não estruturados
Instalação e Setup
# Docker (recomendado para desenvolvimento)
docker run -d \
--name redis \
-p 6379:6379 \
-v redis-data:/data \
redis:7-alpine \
redis-server --appendonly yes --requirepass "minha-senha"
# Docker Compose
# services:
# redis:
# image: redis:7-alpine
# ports: ["6379:6379"]
# volumes: [redis-data:/data]
# command: redis-server --appendonly yes
# Linux (Ubuntu/Debian)
sudo apt install redis-server
sudo systemctl start redis
# macOS
brew install redis
brew services start redis
# Verificar instalação
redis-cli ping # deve retornar PONG
redis-cli info server | grep redis_versionConectar ao CLI
# Conexão local
redis-cli
# Com senha
redis-cli -a minha-senha
# Host e porta customizados
redis-cli -h 192.168.1.100 -p 6380
# Selecionar banco (0-15)
redis-cli -n 1
# Dentro do CLI:
AUTH minha-senha # autenticar
SELECT 0 # selecionar banco 0
DBSIZE # número de chaves no banco atual
FLUSHDB # CUIDADO: apaga todas as chaves do banco atual
FLUSHALL # CUIDADO: apaga todos os bancos
DEBUG SLEEP 0 # teste de latênciaPersistência: RDB e AOF
RDB (Redis Database Backup — Snapshots)
# redis.conf
# Salvar snapshot a cada N segundos se M chaves mudaram
save 900 1 # a cada 15min se ao menos 1 chave mudou
save 300 10 # a cada 5min se ao menos 10 chaves mudaram
save 60 10000 # a cada 1min se ao menos 10000 chaves mudaram
dbfilename dump.rdb
dir /var/lib/redis
# Forçar snapshot manualmente
BGSAVE # background (não bloqueia)
SAVE # síncrono (BLOQUEIA o servidor — evitar em produção)
LASTSAVE # timestamp do último saveAOF (Append-Only File)
# redis.conf
appendonly yes
appendfilename "appendonly.aof"
# Política de sync:
# always — sync a cada write (mais seguro, mais lento)
# everysec — sync a cada segundo (balanço ideal)
# no — deixa o OS decidir (mais rápido, menos seguro)
appendfsync everysec
# Compactar AOF automaticamente
auto-aof-rewrite-percentage 100 # reescreve quando dobrar de tamanho
auto-aof-rewrite-min-size 64mb # mínimo 64MB
# Reescrever AOF manualmente (reduz tamanho)
BGREWRITEAOFRecomendação de Persistência
# Produção (segurança máxima): usar ambos
appendonly yes
save 900 1
# Cache puro (sem persistência):
# No redis.conf, comentar/remover todas as diretivas save
# appendonly no
# Reiniciar limpo em caso de falha é aceitávelTipos de Dados — Strings
O tipo mais básico. Pode armazenar texto, números inteiros, floats ou dados binários (máx 512MB).
# SET e GET básicos
SET nome "Alice"
GET nome # "Alice"
# SET com expiração
SET sessao:123 "dados" EX 3600 # expira em 3600 segundos
SET token:abc "xyz" PX 60000 # expira em 60000 milissegundos
SET chave "valor" EXAT 1700000000 # expira em Unix timestamp
# SET apenas se não existir (NX) ou apenas se existir (XX)
SET lock:recurso "worker-1" NX EX 30 # distributed lock
SET config:theme "dark" XX # atualiza só se já existe
# SETEX / SETNX (formas antigas, mas ainda válidas)
SETEX sessao:456 3600 "dados"
SETNX lock:arquivo "worker-2" # retorna 1 (sucesso) ou 0 (já existe)
# Operações atômicas em inteiros
SET contador 0
INCR contador # 1 (incrementa 1)
INCRBY contador 5 # 6 (incrementa N)
DECR contador # 5
DECRBY contador 2 # 3
INCRBYFLOAT preco 0.5 # incrementa float
# Operações em strings
APPEND mensagem " mundo" # concatena
STRLEN chave # comprimento em bytes
GETSET chave "novo" # retorna valor antigo e seta novo (deprecated, use GETDEL + SET)
GETDEL chave # retorna e deleta (Redis 6.2+)
GETEX chave EX 60 # retorna e atualiza expiração (Redis 6.2+)
# Múltiplas chaves de uma vez
MSET nome "Alice" idade "30" cidade "SP"
MGET nome idade cidade # retorna ["Alice", "30", "SP"]
MSETNX k1 v1 k2 v2 # seta múltiplos apenas se NENHUM existir
# Manipulação de bits
SETBIT bitmap:usuarios 1001 1 # seta bit na posição 1001 (userId)
GETBIT bitmap:usuarios 1001 # 1
BITCOUNT bitmap:usuarios # contar bits 1 (usuários ativos)
BITPOS bitmap:usuarios 1 # posição do primeiro bit 1Tipos de Dados — Hash
Ideal para objetos com múltiplos campos. Eficiente para representar entidades.
# Setar campos
HSET usuario:1 nome "Alice" email "alice@ex.com" idade 30
HSET usuario:1 cidade "São Paulo" # adicionar/atualizar campo
# Ler campos
HGET usuario:1 nome # "Alice"
HMGET usuario:1 nome email # ["Alice", "alice@ex.com"]
HGETALL usuario:1 # todos os campos e valores
# Verificar existência e tamanho
HEXISTS usuario:1 nome # 1 (existe)
HEXISTS usuario:1 telefone # 0 (não existe)
HLEN usuario:1 # número de campos
# Listar chaves/valores
HKEYS usuario:1 # [nome, email, idade, cidade]
HVALS usuario:1 # [Alice, alice@ex.com, 30, São Paulo]
# Incrementar campos numéricos
HINCRBY usuario:1 pontos 10
HINCRBYFLOAT produto:5 preco 1.50
# Deletar campos
HDEL usuario:1 cidade
HDEL usuario:1 campo1 campo2 # múltiplos de uma vez
# HSETNX — seta apenas se não existir
HSETNX usuario:1 role "admin"
# Scan incremental (não bloqueia o servidor)
HSCAN usuario:1 0 MATCH "nome*" COUNT 10Tipos de Dados — List
Listas duplamente encadeadas. Ótimas para filas, stacks e históricos.
# Adicionar elementos
LPUSH fila "tarefa3" "tarefa4" # insere no início: [tarefa4, tarefa3]
RPUSH fila "tarefa5" "tarefa6" # insere no final: [tarefa4, tarefa3, tarefa5, tarefa6]
# Leitura não-destrutiva
LLEN fila # comprimento: 4
LRANGE fila 0 -1 # todos os elementos (índice -1 = último)
LRANGE fila 0 2 # primeiros 3 elementos
LINDEX fila 0 # elemento no índice 0
# Remoção
LPOP fila # remove e retorna do início
RPOP fila # remove e retorna do final
LPOP fila 3 # remove e retorna os 3 primeiros (Redis 6.2+)
# Bloqueante — ideal para workers/consumers
BLPOP fila1 fila2 10 # bloqueia até 10 segundos esperando elemento
BRPOP fila1 fila2 10 # mesmo, mas pelo final
# Transferência atômica entre listas
LMOVE fila:pending fila:processing LEFT RIGHT # move primeiro de pending para processing
BRPOPLPUSH fila:src fila:dst 10 # (deprecated em 6.2, use BLMOVE)
# Modificação
LSET fila 0 "novo-valor" # substituir elemento por índice
LINSERT fila BEFORE "tarefa3" "nova" # inserir antes de um pivô
LINSERT fila AFTER "tarefa3" "nova"
# Remoção de valores específicos
LREM fila 0 "tarefa3" # remover todas as ocorrências de "tarefa3"
LREM fila 2 "duplicada" # remover as 2 primeiras ocorrências
LREM fila -2 "x" # remover as 2 últimas
# Trimmar lista (manter apenas N elementos)
LTRIM fila 0 99 # manter apenas os 100 primeiros (índice 0 a 99)Padrão de Fila com List
# Producer (qualquer cliente)
RPUSH queue:emails '{"to":"alice@ex.com","subject":"Bem-vindo"}'
# Consumer (worker)
# Loop infinito com BLPOP
BLPOP queue:emails 0 # 0 = aguardar indefinidamente
# Fila confiável: mover para lista "em processamento"
LMOVE queue:emails queue:processing LEFT LEFT
# Após processar com sucesso:
LREM queue:processing 1 "<conteudo>"
# Em caso de falha (reprocessar):
LMOVE queue:processing queue:emails RIGHT LEFTTipos de Dados — Set
Conjuntos não-ordenados de strings únicas. Ótimos para tags, memberships e operações de conjunto.
# Adicionar membros
SADD tags:post:1 "nodejs" "javascript" "backend"
SADD tags:post:2 "javascript" "frontend" "vue"
# Verificar e contar
SISMEMBER tags:post:1 "nodejs" # 1 (pertence)
SISMEMBER tags:post:1 "python" # 0 (não pertence)
SMISMEMBER tags:post:1 "nodejs" "python" # [1, 0] (Redis 6.2+)
SCARD tags:post:1 # número de membros: 3
# Listar membros
SMEMBERS tags:post:1 # ["nodejs", "javascript", "backend"] (ordem aleatória)
SRANDMEMBER tags:post:1 2 # 2 membros aleatórios (não remove)
SPOP tags:post:1 # remove e retorna membro aleatório
SPOP tags:post:1 2 # remove e retorna 2 membros aleatórios
# Remover membros
SREM tags:post:1 "backend"
# Operações de conjunto
SUNION tags:post:1 tags:post:2 # união: todas as tags
SINTER tags:post:1 tags:post:2 # interseção: tags em comum
SDIFF tags:post:1 tags:post:2 # diferença: tags só no post:1
# Armazenar resultado de operação
SUNIONSTORE resultado tags:post:1 tags:post:2
SINTERSTORE resultado tags:post:1 tags:post:2
SDIFFSTORE resultado tags:post:1 tags:post:2
# Mover membro entre sets
SMOVE tags:post:1 tags:post:2 "javascript"
# Scan incremental
SSCAN tags:post:1 0 MATCH "java*" COUNT 10Tipos de Dados — Sorted Set (ZSet)
Conjuntos ordenados por score (float). Ideais para rankings, leaderboards e fila prioritizada.
# Adicionar com score
ZADD ranking 1500 "alice"
ZADD ranking 2300 "bob"
ZADD ranking 1800 "carol"
ZADD ranking NX 1200 "dave" # adicionar apenas se não existir
ZADD ranking XX 2500 "bob" # atualizar apenas se existir
ZADD ranking GT 2400 "bob" # atualizar apenas se novo score for maior
ZADD ranking LT 2100 "alice" # atualizar apenas se novo score for menor
# Incrementar score
ZINCRBY ranking 100 "alice" # score de alice agora é 1600 (ou 1601)
# Leitura por rank (posição) — ordem crescente
ZRANGE ranking 0 -1 # todos (menor para maior score)
ZRANGE ranking 0 2 # top 3 menores scores
ZRANGE ranking 0 -1 WITHSCORES # com os scores
ZRANGE ranking 0 -1 REV # ordem decrescente (Redis 6.2+)
ZRANGE ranking 0 2 REV WITHSCORES # top 3 maiores com scores
# Formas antigas (ainda funcionam)
ZREVRANGE ranking 0 2 WITHSCORES # top 3 (maior score primeiro)
# Leitura por score
ZRANGEBYSCORE ranking 1000 2000 # scores entre 1000 e 2000
ZRANGEBYSCORE ranking -inf +inf # todos
ZRANGEBYSCORE ranking "(1000" 2000 # exclusive: score > 1000
ZRANGEBYSCORE ranking 1000 +inf LIMIT 0 10 # paginação
# Rank e score de um membro
ZRANK ranking "alice" # posição (0-indexed, menor score = 0)
ZREVRANK ranking "alice" # posição (maior score = 0)
ZSCORE ranking "alice" # score atual
# Contar membros
ZCARD ranking # total de membros
ZCOUNT ranking 1000 2000 # membros com score entre 1000-2000
ZLEXCOUNT ranking "[a" "[z" # por ordem lexicográfica (scores iguais)
# Remover membros
ZREM ranking "dave"
ZREMRANGEBYRANK ranking 0 2 # remover os 3 piores
ZREMRANGEBYSCORE ranking -inf 1000 # remover score < 1000
# Operações entre sorted sets
ZUNIONSTORE dest 2 zset1 zset2
ZINTERSTORE dest 2 zset1 zset2 WEIGHTS 1 2 # peso diferente para cada setExpiração de Chaves (TTL)
# Definir expiração
EXPIRE chave 60 # expira em 60 segundos
PEXPIRE chave 60000 # expira em 60000 milissegundos
EXPIREAT chave 1700000000 # expira em Unix timestamp (segundos)
PEXPIREAT chave 1700000000000 # em milissegundos
# Verificar TTL
TTL chave # segundos restantes; -1=sem expiração; -2=não existe
PTTL chave # milissegundos restantes
# Remover expiração (tornar permanente)
PERSIST chave
# EXPIRE com opções (Redis 7.0+)
EXPIRE chave 60 NX # seta TTL apenas se não tiver expiração
EXPIRE chave 60 XX # seta TTL apenas se já tiver expiração
EXPIRE chave 60 GT # seta TTL apenas se novo TTL for maior
EXPIRE chave 60 LT # seta TTL apenas se novo TTL for menor
# Pattern: SET com TTL em uma operação
SET sessao:abc "dados" EX 3600Comandos Gerais (Key Space)
# Verificar existência
EXISTS chave # 1 se existe, 0 se não
EXISTS k1 k2 k3 # conta quantas das chaves existem
# Deletar
DEL chave # síncrono
UNLINK chave # assíncrono (non-blocking, recomendado para grandes chaves)
# Renomear
RENAME origem destino # falha se origem não existe
RENAMENX origem destino # renomeia apenas se destino não existe
# Tipo da chave
TYPE chave # string, hash, list, set, zset, stream
# Serializar e restaurar
DUMP chave # serializa para binário (útil para migração)
RESTORE destino 0 <binary> # restaurar de dump
# Buscar chaves por padrão (CUIDADO: bloqueia em produção!)
KEYS "user:*" # lista todas as chaves que começam com "user:"
KEYS "*" # todas as chaves — NUNCA usar em produção!
# SCAN: busca segura e incremental
SCAN 0 MATCH "user:*" COUNT 100
# Retorna: [cursor, [chaves...]]
# Continue chamando até cursor == 0
# Mover chave para outro banco
MOVE chave 1 # move para o banco 1
# Debug e info
OBJECT ENCODING chave # encoding interno (int, embstr, hashtable, etc.)
OBJECT IDLETIME chave # segundos desde último acesso
OBJECT REFCOUNT chave # contagem de referências
DEBUG OBJECT chave # informações detalhadas
# WAIT — garantir replicação antes de continuar
WAIT 1 100 # aguardar 1 réplica confirmar, timeout 100msPadrões de Cache
Cache-Aside (Lazy Loading)
O padrão mais comum. A aplicação verifica o cache antes de ir ao banco.
// Node.js com ioredis
const Redis = require('ioredis');
const redis = new Redis();
async function getUserById(id) {
const cacheKey = `user:${id}`;
// 1. Verificar cache primeiro
const cached = await redis.get(cacheKey);
if (cached) {
return JSON.parse(cached); // cache hit
}
// 2. Cache miss — buscar no banco
const user = await db.query('SELECT * FROM users WHERE id = $1', [id]);
if (!user) return null;
// 3. Popular o cache com TTL
await redis.setex(cacheKey, 3600, JSON.stringify(user)); // 1 hora
return user;
}
// Invalidar cache ao atualizar
async function updateUser(id, data) {
await db.query('UPDATE users SET ...', [id, ...data]);
await redis.del(`user:${id}`); // invalida o cache
}Write-Through
Escreve no cache e no banco de dados ao mesmo tempo.
async function updateUser(id, data) {
// Escrever no banco
const updated = await db.query('UPDATE users SET ... RETURNING *', [id, ...data]);
// Atualizar cache imediatamente
const cacheKey = `user:${id}`;
await redis.setex(cacheKey, 3600, JSON.stringify(updated));
return updated;
}
// Vantagem: cache sempre consistente
// Desvantagem: latência de escrita maior (dois I/Os)Read-Through
O cache gerencia a lógica de fallback automaticamente (geralmente via biblioteca como Cacheable).
// Usando uma camada de abstração
const cache = {
async get(key, fetchFn, ttl = 3600) {
const cached = await redis.get(key);
if (cached) return JSON.parse(cached);
const fresh = await fetchFn();
if (fresh) {
await redis.setex(key, ttl, JSON.stringify(fresh));
}
return fresh;
}
};
// Uso limpo
const user = await cache.get(
`user:${id}`,
() => db.query('SELECT * FROM users WHERE id = $1', [id]),
1800 // 30 minutos
);Cache com Versionamento (evitar invalidação total)
// Versão da chave permite invalidar sem del
const VERSION = 'v2'; // mude para invalidar todos
const cacheKey = `${VERSION}:user:${id}`;
// Ou usar hash do schema como versão
const schemaVersion = createHash('md5').update(schema).digest('hex').slice(0, 8);Pub/Sub
Redis como message broker básico para comunicação em tempo real.
# Terminal 1 — Subscriber
SUBSCRIBE canal:notificacoes
PSUBSCRIBE "canal:*" # padrão glob (pattern subscribe)
PSUBSCRIBE "evento:[0-9]+" # com regex
# Terminal 2 — Publisher
PUBLISH canal:notificacoes '{"tipo":"alerta","msg":"Sistema atualizado"}'
# Listar canais ativos
PUBSUB CHANNELS # todos os canais com subscribers
PUBSUB CHANNELS "canal:*" # por padrão
PUBSUB NUMSUB canal:notificacoes # contagem de subscribers por canal
PUBSUB NUMPAT # número de pattern subscriptions// Node.js — Pub/Sub com ioredis
const Redis = require('ioredis');
// Subscriber — conexão dedicada (não pode fazer outros comandos)
const subscriber = new Redis();
subscriber.subscribe('canal:notificacoes');
subscriber.on('message', (channel, message) => {
const data = JSON.parse(message);
console.log(`[${channel}]`, data);
// processar notificação...
});
// Pattern subscribe
subscriber.psubscribe('evento:*');
subscriber.on('pmessage', (pattern, channel, message) => {
console.log(`Pattern ${pattern} → ${channel}:`, message);
});
// Publisher — conexão normal
const publisher = new Redis();
async function notificar(tipo, dados) {
const message = JSON.stringify({ tipo, dados, ts: Date.now() });
const receivers = await publisher.publish('canal:notificacoes', message);
console.log(`Mensagem enviada para ${receivers} subscribers`);
}
notificar('pedido-novo', { id: 123, valor: 99.90 });Limitações do Pub/Sub simples:
- Mensagens perdidas se nenhum subscriber estiver ouvindo
- Não há persistência
- Sem grupos de consumidores
Use Streams para casos que requerem persistência e grupos.
Redis Streams
Streams são logs append-only com suporte a grupos de consumidores. Similares ao Kafka.
# Adicionar mensagens
XADD eventos * tipo "pedido-novo" usuario "alice" valor "99.90"
# * = ID automático (timestamp-sequência, ex: 1700000000000-0)
# ID customizado
XADD eventos 1700000000000-1 tipo "pagamento" status "aprovado"
# Com limite de tamanho (MAXLEN)
XADD eventos MAXLEN ~ 1000 * tipo "log" msg "Usuário fez login"
# ~ = aproximado (mais eficiente que exato)
# Leitura
XREAD COUNT 10 STREAMS eventos 0-0 # ler a partir do início
XREAD COUNT 10 STREAMS eventos $ # ler apenas novos (em tempo real)
XREAD BLOCK 0 STREAMS eventos $ # bloqueante, aguardar indefinidamente
# Range de IDs
XRANGE eventos - + # todos os registros
XRANGE eventos 1700000000000 + # a partir de timestamp
XRANGE eventos - + COUNT 10 # primeiros 10
XREVRANGE eventos + - # ordem reversa (mais recente primeiro)
# Informações
XLEN eventos # número de entradas
XINFO STREAM eventos # informações detalhadas
XINFO GROUPS eventos # grupos de consumidores
# Deletar entradas
XDEL eventos "1700000000000-0"
XTRIM eventos MAXLEN 500 # manter apenas as 500 mais recentesGrupos de Consumidores (Consumer Groups)
# Criar grupo ($ = começar com mensagens novas)
XGROUP CREATE eventos grupo-processadores $ MKSTREAM
XGROUP CREATE eventos grupo-analytics 0 # processar desde o início
# Ler como membro de um grupo
XREADGROUP GROUP grupo-processadores worker-1 COUNT 5 STREAMS eventos >
# > = entregar apenas mensagens não entregues a este grupo
# Confirmar processamento
XACK eventos grupo-processadores "1700000000000-0"
# Ver mensagens pendentes (entregues mas não confirmadas)
XPENDING eventos grupo-processadores - + 10
XPENDING eventos grupo-processadores - + 10 worker-1 # de um worker específico
# Re-entregar mensagens presas (mais de 5min sem ACK)
XCLAIM eventos grupo-processadores worker-2 300000 "1700000000000-0"
# 300000 = min-idle-time em ms
# Apagar grupo
XGROUP DESTROY eventos grupo-processadores
# Apagar consumidor do grupo
XGROUP DELCONSUMER eventos grupo-processadores worker-1// Consumer Group em Node.js
const Redis = require('ioredis');
const redis = new Redis();
const STREAM = 'eventos';
const GROUP = 'processadores';
const CONSUMER = `worker-${process.pid}`;
async function setupStream() {
try {
await redis.xgroup('CREATE', STREAM, GROUP, '$', 'MKSTREAM');
} catch (err) {
if (!err.message.includes('BUSYGROUP')) throw err;
// Grupo já existe, tudo ok
}
}
async function processMessages() {
await setupStream();
while (true) {
// Ler até 10 mensagens não entregues ao grupo
const results = await redis.xreadgroup(
'GROUP', GROUP, CONSUMER,
'COUNT', 10,
'BLOCK', 2000, // bloquear por 2 segundos
'STREAMS', STREAM, '>'
);
if (!results) continue; // timeout, nenhuma mensagem
for (const [stream, messages] of results) {
for (const [id, fields] of messages) {
try {
// Converter array de fields para objeto
const data = {};
for (let i = 0; i < fields.length; i += 2) {
data[fields[i]] = fields[i + 1];
}
console.log(`Processando ${id}:`, data);
await processarEvento(data);
// Confirmar processamento
await redis.xack(STREAM, GROUP, id);
} catch (err) {
console.error(`Erro processando ${id}:`, err);
// Não faz ACK — mensagem ficará pendente para reprocessamento
}
}
}
}
}
processMessages();Transações (MULTI/EXEC)
# MULTI inicia um bloco transacional
MULTI
# Comandos são enfileirados
SET conta:alice 1000
DECRBY conta:alice 200
INCRBY conta:bob 200
# EXEC executa tudo atomicamente
EXEC
# Retorna: [OK, 800, 700]
# DISCARD cancela a transação
MULTI
SET temp "valor"
DISCARD # descarta todos os comandos enfileiradosOtimistic Locking com WATCH
# WATCH monitora chaves — se alteradas, EXEC falha
WATCH conta:alice
# Ler valor atual
GET conta:alice # 1000
MULTI
DECRBY conta:alice 200
INCRBY conta:bob 200
EXEC
# Se conta:alice foi alterada por outro cliente entre WATCH e EXEC:
# EXEC retorna nil (transação falhou, deve tentar de novo)// Retry loop com WATCH
async function transferir(de, para, valor) {
const MAX_TENTATIVAS = 10;
for (let tentativa = 0; tentativa < MAX_TENTATIVAS; tentativa++) {
// Monitorar a chave
await redis.watch(`conta:${de}`);
const saldo = parseInt(await redis.get(`conta:${de}`), 10);
if (saldo < valor) {
await redis.unwatch();
throw new Error('Saldo insuficiente');
}
// Executar transação
const pipeline = redis.multi();
pipeline.decrby(`conta:${de}`, valor);
pipeline.incrby(`conta:${para}`, valor);
const resultado = await pipeline.exec();
if (resultado !== null) {
// Sucesso! exec retorna array de resultados
return resultado;
}
// null = WATCH detectou mudança, tentar de novo
}
throw new Error('Transação falhou após várias tentativas');
}Pipeline
// Sem pipeline: N round-trips
for (const user of users) {
await redis.set(`user:${user.id}`, JSON.stringify(user));
}
// Com pipeline: 1 round-trip
const pipeline = redis.pipeline();
for (const user of users) {
pipeline.set(`user:${user.id}`, JSON.stringify(user), 'EX', 3600);
}
const results = await pipeline.exec();
// results: [[null, 'OK'], [null, 'OK'], ...]
// Primeiro elemento é erro, segundo é resultadoLua Scripting
Scripts Lua são executados atomicamente no servidor Redis — sem risco de interrupção.
# Executar script Lua
EVAL "return redis.call('SET', KEYS[1], ARGV[1])" 1 minha-chave "valor"
# EVAL script numkeys key [key ...] arg [arg ...]
# Exemplo: incrementar com valor máximo (atômico)
EVAL "
local atual = tonumber(redis.call('GET', KEYS[1])) or 0
local maximo = tonumber(ARGV[1])
if atual >= maximo then
return 0
end
redis.call('INCR', KEYS[1])
return 1
" 1 contador 100
# Carregar script (retorna SHA1 do script)
SCRIPT LOAD "return redis.call('GET', KEYS[1])"
# Retorna: "e0e1f9fabfa9d353e7b7b9b5d14a19b2b57a0b4d"
# Executar pelo SHA1 (mais eficiente — script já está em cache no servidor)
EVALSHA e0e1f9fabfa9d353e7b7b9b5d14a19b2b57a0b4d 1 minha-chave
# Verificar se script está em cache
SCRIPT EXISTS e0e1f9fabfa9d353e7b7b9b5d14a19b2b57a0b4d
# Limpar cache de scripts
SCRIPT FLUSH// Distributed lock com Lua (garante atomicidade)
const lockScript = `
local current = redis.call('GET', KEYS[1])
if current == false then
redis.call('SET', KEYS[1], ARGV[1], 'PX', ARGV[2])
return 1
elseif current == ARGV[1] then
-- Renovar TTL se já somos o dono
redis.call('PEXPIRE', KEYS[1], ARGV[2])
return 1
else
return 0
end
`;
const unlockScript = `
if redis.call('GET', KEYS[1]) == ARGV[1] then
return redis.call('DEL', KEYS[1])
else
return 0
end
`;
class DistributedLock {
constructor(redis, key, ttlMs = 30000) {
this.redis = redis;
this.key = `lock:${key}`;
this.ttlMs = ttlMs;
this.token = Math.random().toString(36).slice(2);
}
async acquire(maxWaitMs = 5000) {
const deadline = Date.now() + maxWaitMs;
while (Date.now() < deadline) {
const result = await this.redis.eval(
lockScript, 1, this.key, this.token, this.ttlMs
);
if (result === 1) return true;
await new Promise(r => setTimeout(r, 50));
}
return false;
}
async release() {
return this.redis.eval(unlockScript, 1, this.key, this.token);
}
}Redis Sentinel vs Cluster
Redis Sentinel (Alta Disponibilidade)
# sentinel.conf
sentinel monitor meu-master 127.0.0.1 6379 2
# 2 = quórum (número de sentinels que precisam concordar em failover)
sentinel auth-pass meu-master senha123
sentinel down-after-milliseconds meu-master 5000 # 5s para considerar down
sentinel failover-timeout meu-master 60000 # timeout do failover
sentinel parallel-syncs meu-master 1 # réplicas sync por vez
# Iniciar sentinel
redis-sentinel /etc/redis/sentinel.conf
# ou: redis-server /etc/redis/sentinel.conf --sentinel// Conectar via Sentinel
const redis = new Redis({
sentinels: [
{ host: '192.168.1.1', port: 26379 },
{ host: '192.168.1.2', port: 26379 },
{ host: '192.168.1.3', port: 26379 }
],
name: 'meu-master', // nome do master no sentinel.conf
password: 'senha123'
});Redis Cluster (Escalabilidade Horizontal)
# Criar cluster com 3 masters + 3 réplicas
redis-cli --cluster create \
127.0.0.1:7000 \
127.0.0.1:7001 \
127.0.0.1:7002 \
127.0.0.1:7003 \
127.0.0.1:7004 \
127.0.0.1:7005 \
--cluster-replicas 1
# Verificar status
redis-cli --cluster check 127.0.0.1:7000
# Hash slots: 16384 slots divididos entre masters
# Cada chave é mapeada para um slot via CRC16// Conectar ao Cluster
const { Cluster } = require('ioredis');
const cluster = new Cluster([
{ host: '127.0.0.1', port: 7000 },
{ host: '127.0.0.1', port: 7001 },
{ host: '127.0.0.1', port: 7002 }
], {
redisOptions: { password: 'senha123' },
scaleReads: 'slave' // distribuir leituras entre réplicas
});
// Hash tags: garantir que chaves relacionadas fiquem no mesmo slot
// Coloque a parte variável entre {}
await cluster.set('{user:123}:profile', JSON.stringify(profile));
await cluster.set('{user:123}:settings', JSON.stringify(settings));
// Ambas as chaves ficam no mesmo slot (baseado em "user:123")Configuração e Tuning
# redis.conf — configurações importantes de produção
# Memória máxima
maxmemory 2gb
# Política de evicção quando memória cheia:
# noeviction — retorna erro (não remove nada)
# allkeys-lru — remove qualquer chave LRU (recomendado para cache)
# volatile-lru — remove chaves com TTL usando LRU
# allkeys-lfu — LFU (menos frequentemente usado) — Redis 4.0+
# volatile-ttl — remove chave com menor TTL primeiro
maxmemory-policy allkeys-lru
# Performance
hz 10 # frequência do event loop (10-100)
tcp-backlog 511 # fila de conexões TCP pendentes
tcp-keepalive 300 # keepalive em segundos
# Slowlog — registrar comandos lentos
slowlog-log-slower-than 10000 # microssegundos (10ms)
slowlog-max-len 128
# Checar slowlog
SLOWLOG GET 10 # últimos 10 comandos lentos
SLOWLOG LEN # total de entradas no slowlog
SLOWLOG RESET # limpar
# Lazy freeing (non-blocking DEL/FLUSH)
lazyfree-lazy-eviction yes
lazyfree-lazy-expire yes
lazyfree-lazy-server-del yes
# Desabilitar comandos perigosos
rename-command FLUSHALL "" # desabilitar
rename-command FLUSHDB "FLUSHDB_SECRET_123" # renomear
rename-command CONFIG "" # desabilitar CONFIG em produção
# Análise de memória
MEMORY USAGE chave # bytes usados por uma chave
MEMORY DOCTOR # diagnóstico e recomendações
MEMORY STATS # estatísticas de memória
INFO memory # info detalhada de memória
# Monitor em tempo real (CUIDADO: impacto de performance)
MONITOR # exibe todos os comandos em tempo realRedis com Java
Jedis (simples)
// build.gradle
// implementation 'redis.clients:jedis:5.1.0'
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
// Pool de conexões
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(20);
poolConfig.setMaxIdle(10);
poolConfig.setMinIdle(2);
poolConfig.setTestOnBorrow(true);
JedisPool pool = new JedisPool(poolConfig, "localhost", 6379, 2000, "senha");
// Usar conexão do pool
try (Jedis jedis = pool.getResource()) {
// String
jedis.set("nome", "Alice");
String nome = jedis.get("nome");
// Com TTL
jedis.setex("sessao:123", 3600, "dados");
// Hash
jedis.hset("user:1", Map.of("nome", "Alice", "email", "alice@ex.com"));
Map<String, String> user = jedis.hgetAll("user:1");
// Pipeline
Pipeline pipeline = jedis.pipelined();
for (int i = 0; i < 1000; i++) {
pipeline.set("key:" + i, "value:" + i);
}
List<Object> results = pipeline.syncAndReturnAll();
}Lettuce (reativo, thread-safe)
// build.gradle
// implementation 'io.lettuce:lettuce-core:6.3.0.RELEASE'
import io.lettuce.core.RedisClient;
import io.lettuce.core.api.StatefulRedisConnection;
import io.lettuce.core.api.sync.RedisCommands;
RedisClient client = RedisClient.create("redis://senha@localhost:6379/0");
StatefulRedisConnection<String, String> connection = client.connect();
RedisCommands<String, String> commands = connection.sync();
// Comandos síncronos
commands.set("nome", "Alice");
String nome = commands.get("nome");
commands.expire("nome", 3600);
// Async com Lettuce
var asyncCommands = connection.async();
asyncCommands.get("nome")
.thenAccept(value -> System.out.println("Valor: " + value));
// Reactive com Lettuce
var reactiveCommands = connection.reactive();
reactiveCommands.get("nome")
.subscribe(value -> System.out.println("Valor: " + value));
// Fechar recursos
connection.close();
client.shutdown();Spring Data Redis
// application.yaml
// spring.data.redis.host: localhost
// spring.data.redis.port: 6379
// spring.data.redis.password: senha
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
return template;
}
}
@Service
public class CacheService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public void set(String key, Object value, long ttlSeconds) {
redisTemplate.opsForValue()
.set(key, value, Duration.ofSeconds(ttlSeconds));
}
public Object get(String key) {
return redisTemplate.opsForValue().get(key);
}
// Usar @Cacheable, @CachePut, @CacheEvict
@Cacheable(value = "users", key = "#id")
public User findById(Long id) {
return userRepository.findById(id).orElseThrow();
}
@CacheEvict(value = "users", key = "#id")
public void deleteUser(Long id) {
userRepository.deleteById(id);
}
}Redis com Node.js (ioredis)
const Redis = require('ioredis'); // npm install ioredis
// Conexão básica
const redis = new Redis({
host: 'localhost',
port: 6379,
password: 'senha',
db: 0,
retryStrategy: (times) => {
// Reconectar com backoff exponencial
const delay = Math.min(times * 50, 2000);
return delay;
},
maxRetriesPerRequest: 3,
lazyConnect: false,
keepAlive: 5000,
enableReadyCheck: true
});
redis.on('connect', () => console.log('Redis conectado'));
redis.on('error', (err) => console.error('Redis erro:', err));
redis.on('reconnecting', () => console.log('Reconectando...'));
// Uso básico
await redis.set('key', 'value', 'EX', 3600);
const value = await redis.get('key');
const ttl = await redis.ttl('key');
// JSON
await redis.set('user:1', JSON.stringify({ name: 'Alice' }), 'EX', 3600);
const user = JSON.parse(await redis.get('user:1'));
// Hash
await redis.hset('user:2', 'name', 'Bob', 'age', '25');
const name = await redis.hget('user:2', 'name');
const all = await redis.hgetall('user:2');
// Pipeline
const results = await redis.pipeline()
.set('a', 1)
.set('b', 2)
.get('a')
.get('b')
.exec();
// [[null, 'OK'], [null, 'OK'], [null, '1'], [null, '2']]
// Pub/Sub
const sub = new Redis();
await sub.subscribe('canal');
sub.on('message', (channel, msg) => console.log(channel, msg));
const pub = new Redis();
await pub.publish('canal', 'olá!');Casos de Uso
Session Store
const session = require('express-session');
const RedisStore = require('connect-redis').default;
const { createClient } = require('redis'); // node-redis v4
const redisClient = createClient({ url: 'redis://localhost:6379' });
await redisClient.connect();
app.use(session({
store: new RedisStore({ client: redisClient }),
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
secure: process.env.NODE_ENV === 'production',
httpOnly: true,
maxAge: 24 * 60 * 60 * 1000 // 24 horas
}
}));Rate Limiting
async function rateLimiter(userId, maxRequests = 100, windowSec = 60) {
const key = `rate:${userId}`;
const multi = redis.multi();
multi.incr(key);
multi.ttl(key);
const [[, count], [, ttl]] = await multi.exec();
if (ttl < 0) {
// Primeira requisição na janela, setar expiração
await redis.expire(key, windowSec);
}
if (count > maxRequests) {
throw new Error(`Rate limit excedido. Tente novamente em ${ttl}s`);
}
return { remaining: maxRequests - count, resetIn: ttl };
}Leaderboard
// Adicionar pontuação
async function addScore(userId, points) {
await redis.zadd('leaderboard', 'GT', points, userId);
}
// Incrementar pontuação
async function incrementScore(userId, points) {
await redis.zincrby('leaderboard', points, userId);
}
// Top N jogadores
async function getTopN(n = 10) {
const results = await redis.zrevrange('leaderboard', 0, n - 1, 'WITHSCORES');
const ranking = [];
for (let i = 0; i < results.length; i += 2) {
ranking.push({
userId: results[i],
score: parseInt(results[i + 1]),
rank: i / 2 + 1
});
}
return ranking;
}
// Rank de um usuário específico
async function getUserRank(userId) {
const rank = await redis.zrevrank('leaderboard', userId);
const score = await redis.zscore('leaderboard', userId);
return { rank: rank + 1, score: parseInt(score) };
}Distributed Lock
class RedLock {
constructor(redis, key, ttlMs = 30000) {
this.redis = redis;
this.key = `redlock:${key}`;
this.ttlMs = ttlMs;
this.token = `${Date.now()}-${Math.random()}`;
}
async acquire(maxRetries = 10, retryDelayMs = 200) {
for (let i = 0; i < maxRetries; i++) {
const result = await this.redis.set(
this.key, this.token, 'NX', 'PX', this.ttlMs
);
if (result === 'OK') return true;
await new Promise(r => setTimeout(r, retryDelayMs));
}
return false;
}
async release() {
const script = `
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
`;
return this.redis.eval(script, 1, this.key, this.token);
}
async withLock(fn) {
const acquired = await this.acquire();
if (!acquired) throw new Error('Não foi possível adquirir o lock');
try {
return await fn();
} finally {
await this.release();
}
}
}
// Uso
const lock = new RedLock(redis, 'processar-pagamento-123');
await lock.withLock(async () => {
// código crítico — garantido exclusivo
await processarPagamento(123);
});
---
## Redis Cluster
O Redis Cluster distribui automaticamente os dados entre múltiplos nós usando **hash slots**, eliminando um único ponto de falha e permitindo escala horizontal de escrita.
### Arquitetura
- O espaço de chaves é dividido em **16.384 hash slots** (0–16383)
- Cada master é responsável por um subconjunto de slots
- Todo nó master pode ter uma ou mais replicas para HA
- O slot de uma chave é calculado como `CRC16(key) % 16384`
- Topologia mínima recomendada: **3 masters + 3 replicas** (1 replica por master)
Nó A (master): slots 0 – 5460 Nó B (master): slots 5461 – 10922 Nó C (master): slots 10923 – 16383
### Criar um cluster
```bash
# Subir 6 instâncias Redis com cluster-enabled
for port in 7001 7002 7003 7004 7005 7006; do
mkdir -p /tmp/redis-cluster/$port
redis-server --port $port \
--cluster-enabled yes \
--cluster-config-file /tmp/redis-cluster/$port/nodes.conf \
--cluster-node-timeout 5000 \
--appendonly yes \
--daemonize yes \
--logfile /tmp/redis-cluster/$port/redis.log
done
# Criar o cluster (--cluster-replicas 1 = 1 replica por master)
redis-cli --cluster create \
127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 \
127.0.0.1:7004 127.0.0.1:7005 127.0.0.1:7006 \
--cluster-replicas 1 \
--cluster-yesComandos de administração
# Informações gerais do cluster
redis-cli -p 7001 CLUSTER INFO
# Listar todos os nós e seus slots
redis-cli -p 7001 CLUSTER NODES
# Verificar status detalhado via redis-cli cluster tool
redis-cli --cluster check 127.0.0.1:7001
# Adicionar novo master ao cluster
redis-cli --cluster add-node 127.0.0.1:7007 127.0.0.1:7001
# Remover nó (após mover todos os slots dele)
redis-cli --cluster del-node 127.0.0.1:7001 <node-id>
# Rebalancear slots igualmente entre masters
redis-cli --cluster rebalance 127.0.0.1:7001 --cluster-use-empty-mastersFailover automático e manual
# O cluster promove automaticamente uma replica quando detecta que o master falhou
# (quórum de masters confirma a falha após cluster-node-timeout ms)
# Failover manual sem perda de dados (a replica sincroniza antes de promover)
redis-cli -p 7004 CLUSTER FAILOVER
# Failover forçado (mesmo se replica estiver desatualizada)
redis-cli -p 7004 CLUSTER FAILOVER FORCEHash tags e operações multi-key
# Problema: MGET/MSET exigem que todas as chaves estejam no mesmo slot
# Solução: hash tags — a parte entre {} determina o slot
# Chaves no mesmo slot (slot calculado sobre "user:42")
SET {user:42}:nome "Rafael"
SET {user:42}:email "rafael@example.com"
MGET {user:42}:nome {user:42}:email # OK — mesmo slot
# Sem hash tag: podem cair em slots diferentes → CROSSSLOT error
MGET user:42:nome user:42:email # ERRO: CROSSSLOT Keys in request don't hash to the same slot
# Conectar com modo cluster no redis-cli
redis-cli -c -p 7001 # -c ativa redirecionamento automático de slotsRedis Sentinel
O Redis Sentinel fornece alta disponibilidade para topologias master-replica sem Cluster. Ele monitora, notifica e realiza failover automático.
Topologia
┌─────────────────────────────────────────┐
│ Master (6379) ←─ replicação ─→ │
│ Replica 1 (6380) │
│ Replica 2 (6381) │
│ │
│ Sentinel 1 (26379) ┐ │
│ Sentinel 2 (26380) ├─ monitoram todos │
│ Sentinel 3 (26381) ┘ │
└─────────────────────────────────────────┘- Mínimo de 3 Sentinels para quórum (maioria decide failover)
- Sentinels podem rodar no mesmo host das instâncias Redis ou separados
- Clientes conectam ao Sentinel para descobrir qual é o master atual
Configuração básica (sentinel.conf)
# sentinel.conf — igual para os 3, ajustar porta
port 26379
daemonize yes
logfile /var/log/redis/sentinel.log
# Monitorar master "mymaster" — quórum = 2 (de 3 sentinels)
sentinel monitor mymaster 127.0.0.1 6379 2
# Quantos ms sem resposta para considerar o master como down
sentinel down-after-milliseconds mymaster 5000
# Quantas replicas podem ser reconfiguradas em paralelo no failover
sentinel parallel-syncs mymaster 1
# Timeout máximo do failover
sentinel failover-timeout mymaster 60000
# Senha do Redis (se configurada)
sentinel auth-pass mymaster minha-senha
# Iniciar
redis-sentinel /etc/redis/sentinel.conf
# ou
redis-server /etc/redis/sentinel.conf --sentinelComandos Sentinel
# Conectar ao Sentinel
redis-cli -p 26379
# Listar masters monitorados
SENTINEL masters
# Detalhes de um master específico
SENTINEL master mymaster
# Listar replicas do master
SENTINEL replicas mymaster
# Listar outros sentinels conhecidos
SENTINEL sentinels mymaster
# Verificar endereço atual do master (usado pelos clientes)
SENTINEL get-master-addr-by-name mymaster
# Retorna: 1) "127.0.0.1" 2) "6379"
# Failover manual
SENTINEL failover mymaster
# Resetar estado do master (útil após mudanças de topologia)
SENTINEL reset mymasterAuto-discovery de master no cliente
// ioredis — conectar via Sentinel (Node.js)
import Redis from 'ioredis';
const redis = new Redis({
sentinels: [
{ host: '127.0.0.1', port: 26379 },
{ host: '127.0.0.1', port: 26380 },
{ host: '127.0.0.1', port: 26381 },
],
name: 'mymaster', // nome do grupo monitorado
password: 'minha-senha', // senha do Redis
sentinelPassword: 'sentinel-senha', // senha dos sentinels (se configurada)
role: 'master', // 'master' para escrita, 'slave' para leitura
});
// O cliente resolve automaticamente qual nó é o master atual
await redis.set('chave', 'valor');# redis-py — conectar via Sentinel (Python)
from redis.sentinel import Sentinel
sentinel = Sentinel(
[('127.0.0.1', 26379), ('127.0.0.1', 26380), ('127.0.0.1', 26381)],
socket_timeout=0.5,
password='minha-senha'
)
master = sentinel.master_for('mymaster', socket_timeout=0.5)
replica = sentinel.slave_for('mymaster', socket_timeout=0.5)
master.set('chave', 'valor')
replica.get('chave')Módulos e Redis Stack
O Redis Stack é uma distribuição oficial que empacota Redis OSS + os principais módulos em uma única imagem Docker, facilitando o uso em desenvolvimento.
Subir Redis Stack com Docker
# Docker (inclui RedisJSON, RedisSearch, RedisTimeSeries, RedisBloom, RedisGraph)
docker run -d \
--name redis-stack \
-p 6379:6379 \
-p 8001:8001 \ # RedisInsight (UI web)
-v redis-stack-data:/data \
redis/redis-stack:latest
# Verificar módulos carregados
redis-cli MODULE LISTRedisJSON
Permite armazenar, recuperar e manipular documentos JSON nativamente, sem serializar/deserializar no lado do cliente.
# JSON.SET — armazenar documento ($ = root)
JSON.SET usuario:1 $ '{"nome":"Rafael","idade":30,"enderecos":[{"cidade":"SP"}]}'
# JSON.GET — recuperar todo o documento
JSON.GET usuario:1
# JSON.GET com path JSONPath
JSON.GET usuario:1 $.nome
JSON.GET usuario:1 $.enderecos[0].cidade
# JSON.MGET — buscar mesmo path em múltiplas chaves
JSON.MGET usuario:1 usuario:2 $.nome
# JSON.SET — atualizar campo específico
JSON.SET usuario:1 $.idade 31
# JSON.NUMINCRBY — incrementar número
JSON.NUMINCRBY usuario:1 $.idade 1
# JSON.ARRAPPEND — adicionar item a array
JSON.ARRAPPEND usuario:1 $.enderecos '{"cidade":"RJ"}'
# JSON.DEL — remover campo ou documento
JSON.DEL usuario:1 $.enderecos[0]
JSON.DEL usuario:1 # remove o documento inteiro
# JSON.TYPE — tipo do valor em um path
JSON.TYPE usuario:1 $.enderecos # arrayRedisSearch
Motor de busca full-text e geoespacial integrado ao Redis. Cria índices invertidos sobre Hashes ou JSONs.
# Criar índice sobre Hashes com campos text, numeric e tag
FT.CREATE idx:produtos ON HASH PREFIX 1 produto:
SCHEMA
nome TEXT WEIGHT 2.0
descricao TEXT
preco NUMERIC SORTABLE
categoria TAG
# Inserir documentos
HSET produto:1 nome "Notebook Pro" descricao "Intel i7 16GB" preco 4500 categoria "eletronicos"
HSET produto:2 nome "Mouse Wireless" descricao "Ergonômico 2.4GHz" preco 120 categoria "eletronicos"
# Busca full-text
FT.SEARCH idx:produtos "notebook"
# Busca com filtro numérico
FT.SEARCH idx:produtos "@categoria:{eletronicos} @preco:[100 500]"
# Busca fuzzy (tolerância a erros de digitação)
FT.SEARCH idx:produtos "%notebok%"
# Criar índice geoespacial
FT.CREATE idx:lojas ON HASH PREFIX 1 loja:
SCHEMA nome TEXT localizacao GEO
HSET loja:1 nome "Loja Centro" localizacao "-46.6333,-23.5505"
# Busca geoespacial (raio de 5km)
FT.SEARCH idx:lojas "@localizacao:[-46.6333 -23.5505 5 km]"
# Listar índices
FT._LIST
# Informações do índice
FT.INFO idx:produtosRedisTimeSeries
Estrutura de dados nativa para séries temporais com compressão, downsampling e agregações eficientes.
# Criar série temporal com rótulos
TS.CREATE temperatura:sensor1 RETENTION 86400000 LABELS sensor sensor1 local sala_servidores
# Inserir ponto (timestamp em ms, ou * para agora)
TS.ADD temperatura:sensor1 * 23.5
TS.ADD temperatura:sensor1 1700000000000 24.1
# Inserir múltiplos pontos
TS.MADD temperatura:sensor1 * 22.8 temperatura:sensor1 * 23.1
# Consultar range
TS.RANGE temperatura:sensor1 1699000000000 1700000000000
# Consultar com agregação (média a cada 5 minutos)
TS.RANGE temperatura:sensor1 - + AGGREGATION avg 300000
# Buscar múltiplas séries por label (requer índice)
TS.MRANGE - + AGGREGATION avg 60000 FILTER local=sala_servidores
# Últimos N valores
TS.REVRANGE temperatura:sensor1 - + COUNT 10
# Criar regra de downsampling (compactar dados antigos)
TS.CREATERULE temperatura:sensor1 temperatura:sensor1:hourly AGGREGATION avg 3600000
# Informações da série
TS.INFO temperatura:sensor1RedisBloom — estruturas probabilísticas
Estruturas de dados que trocam exatidão por performance e uso de memória drasticamente menor.
# ── Bloom Filter ──────────────────────────────────────────────
# Responde "definitivamente NÃO está" ou "provavelmente ESTÁ"
# False positives possíveis, false negatives impossíveis
# Criar Bloom Filter (taxa de falso positivo 0.1%, capacidade 1M itens)
BF.RESERVE emails:vistos 0.001 1000000
# Adicionar item
BF.ADD emails:vistos "usuario@example.com"
# Verificar (0 = definitivamente não está, 1 = provavelmente está)
BF.EXISTS emails:vistos "usuario@example.com" # 1
BF.EXISTS emails:vistos "outro@example.com" # 0
# Adicionar/verificar múltiplos
BF.MADD emails:vistos "a@x.com" "b@x.com"
BF.MEXISTS emails:vistos "a@x.com" "c@x.com"
# ── Count-Min Sketch ──────────────────────────────────────────
# Estimativa de frequência de itens com sub-linear uso de memória
# Inicializar com erro máximo e probabilidade
CMS.INITBYPROB trending:palavras 0.001 0.01
# Incrementar contagem
CMS.INCRBY trending:palavras "redis" 1 "postgresql" 3
# Consultar contagem estimada
CMS.QUERY trending:palavras "redis" "postgresql"
# ── Cuckoo Filter ────────────────────────────────────────────
# Similar ao Bloom, mas permite deletar itens
CF.RESERVE cache:ids 1000000
CF.ADD cache:ids "produto:42"
CF.EXISTS cache:ids "produto:42" # 1
CF.DEL cache:ids "produto:42"
CF.EXISTS cache:ids "produto:42" # 0
# ── Top-K ────────────────────────────────────────────────────
# Mantém os K itens mais frequentes (Heavy Hitter)
TOPK.RESERVE produtos:topk 10 50 4 0.9
TOPK.ADD produtos:topk "produto:1" "produto:2" "produto:1" "produto:3"
TOPK.LIST produtos:topk # lista os top-K atuais
TOPK.QUERY produtos:topk "produto:1" # está no top-K?