Data

Redis

Referência completa de Redis: tipos de dados, comandos, padrões de cache, Pub/Sub, Streams, Lua, Cluster e integrações

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_version

Conectar 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ência

Persistê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 save

AOF (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)
BGREWRITEAOF

Recomendaçã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ável

Tipos 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 1

Tipos 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 10

Tipos 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 LEFT

Tipos 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 10

Tipos 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 set

Expiraçã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 3600

Comandos 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 100ms

Padrõ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 recentes

Grupos 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 enfileirados

Otimistic 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 é resultado

Lua 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 real

Redis 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** (016383)
- 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-yes

Comandos 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-masters

Failover 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 FORCE

Hash 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 slots

Redis 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 --sentinel

Comandos 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 mymaster

Auto-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 LIST

RedisJSON

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   # array

RedisSearch

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:produtos

RedisTimeSeries

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:sensor1

RedisBloom — 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?