kb.erickguedes.com
Redis: Cache, Fila e Tempo Real

RedisJSON, RediSearch e Módulos

Aula 5 de 6

Redis Stack

# Redis Stack inclui todos os módulos
docker run -d \
    --name redis-stack \
    -p 6379:6379 \
    -p 8001:8001 \          # RedisInsight UI
    redis/redis-stack:latest

# Verificar módulos carregados
redis-cli MODULE LIST

# Módulos do Stack:
# - RedisJSON: manipulação de JSON nativa
# - RediSearch: busca full-text e indexação
# - RedisTimeSeries: séries temporais
# - RedisBloom: Bloom Filter e estruturas probabilísticas
# - RedisGraph (LEGACY/READ-ONLY desde 2024)

RedisJSON

# Comandos básicos
JSON.SET product:1 $ '{"name":"Notebook","price":4999.90,"tags":["eletrônicos","promoção"],"specs":{"ram":"16GB","storage":"512GB SSD"}}'

JSON.GET product:1
# {"name":"Notebook","price":4999.90,...}

# JSONPath queries
JSON.GET product:1 $.name              # ["Notebook"]
JSON.GET product:1 $.specs.ram         # ["16GB"]
JSON.GET product:1 $.tags[0]           # ["eletrônicos"]

# Atualizar partes do JSON
JSON.SET product:1 $.price 4499.90     # atualizar preço
JSON.SET product:1 $.tags[1] '"oferta"'

# Array append
JSON.ARRAPPEND product:1 $.tags '"novo"'
JSON.ARRPOP product:1 $.tags           # pop do array
JSON.ARRTRIM product:1 $.tags 0 2      # manter posições 0-2

# Incrementar número
JSON.NUMINCRBY product:1 $.price 500

# Deletar campo
JSON.DEL product:1 $.specs

# Obter metadados
JSON.TYPE product:1 $        # object
JSON.OBJLEN product:1 $      # número de chaves
JSON.STRLEN product:1 $.name # tamanho da string

# Múltiplos documentos
JSON.SET product:2 $ '{"name":"Mouse","price":149.90}'
JSON.MGET product:1 product:2 $.name
# 1) ["Notebook"] 2) ["Mouse"]

JSON Index (RediSearch)

# Criar índice para JSON
FT.CREATE idx_products ON JSON
    PREFIX 1 product:
    SCHEMA
        $.name AS name TEXT WEIGHT 2.0
        $.price AS price NUMERIC SORTABLE
        $.tags[*] AS tags TAG
        $.specs.ram AS ram TEXT

# Buscar
FT.SEARCH idx_products "@name:notebook"
FT.SEARCH idx_products "@price:[4000 6000]"
FT.SEARCH idx_products "@tags:{promoção}"

RediSearch

# INDEXAÇÃO

# Hash index
FT.CREATE idx_users ON HASH
    PREFIX 1 user:
    SCHEMA
        nome TEXT WEIGHT 2.0 SORTABLE
        email TEXT
        idade NUMERIC
        cidade TAG
        ativo TAG

# JSON index (como visto acima)
FT.CREATE idx_articles ON JSON
    PREFIX 1 article:
    SCHEMA
        $.title AS title TEXT WEIGHT 3.0
        $.body AS body TEXT
        $.author AS author TEXT
        $.tags[*] AS tags TAG
        $.views AS views NUMERIC SORTABLE

FT.SEARCH — Queries

# Text search (full-text)
FT.SEARCH idx_users "maria"
FT.SEARCH idx_users "joão silva"

# Fuzzy (prefix match com *)
FT.SEARCH idx_users "mar*"
FT.SEARCH idx_users "*ilva"

# Field-specific
FT.SEARCH idx_users "@nome:joão"
FT.SEARCH idx_users "@email:gmail.com"

# Tags
FT.SEARCH idx_users "@cidade:{São Paulo}"
FT.SEARCH idx_users "@tags:{eletrônicos | informática}"

# Numeric range
FT.SEARCH idx_users "@idade:[18 30]"
FT.SEARCH idx_articles "@views:[1000 +inf]"

# Boolean operators
FT.SEARCH idx_articles "redis OR cache"
FT.SEARCH idx_articles "redis -cluster"      # NOT
FT.SEARCH idx_articles "redis +database"     # AND

# Paginação
FT.SEARCH idx_articles "redis" LIMIT 0 10

# Ordenação
FT.SEARCH idx_articles "redis" SORTBY views DESC

# Highlight
FT.SEARCH idx_articles "maria" HIGHLIGHT TAGS "<b>" "</b>"

# Return specific fields
FT.SEARCH idx_articles "maria" RETURN 2 name email

FT.AGGREGATE

# Agregações estilo GROUP BY
FT.AGGREGATE idx_articles "*"
    GROUPBY 1 @author
    REDUCE COUNT 0 AS total_articles
    REDUCE AVG 1 @views AS avg_views
    SORTBY 2 @total_articles DESC
    LIMIT 0 10

# Por tags
FT.AGGREGATE idx_articles "*"
    GROUPBY 1 @tags
    REDUCE COUNT 0 AS count
    REDUCE SUM 1 @views AS total_views
    SORTBY 2 @count DESC

# Com filtros
FT.AGGREGATE idx_articles "@views:[100 +inf]"
    GROUPBY 1 @author
    REDUCE COUNT 0 AS articles
    REDUCE AVG 1 @views AS avg_views
    HAVING "@avg_views > 500"

FT.EXPLAIN

# Explicar como a query será executada
FT.EXPLAIN idx_articles "redis OR cache -cluster"
# Parser result:
# PREFIX:
#   UNION:
#     +redis(weight=1.00)
#     +cache(weight=1.00)
#   NOT:
#     +cluster(weight=1.00)

Autocomplete (FT.SUGADD)

# Sugestões de autocomplete
FT.SUGADD sug:products "notebook" 100
FT.SUGADD sug:products "notebook dell" 80
FT.SUGADD sug:products "mouse gamer" 90
FT.SUGADD sug:products "monitor 4k" 70

# Buscar sugestões
FT.SUGGET sug:products "not"      # notebook, notebook dell
FT.SUGGET sug:products "mou"      # mouse gamer
FT.SUGGET sug:products "m" 5      # top 5 sugestões

# Com fuzzy
FT.SUGGET sug:products "notbook" FUZZY  # corrige: notebook

# Com scores
FT.SUGGET sug:products "not" WITHSCORES

RedisTimeSeries

# TS.CREATE: criar série temporal
TS.CREATE ts:cpu:server1 RETENTION 86400000 LABELS type cpu host server1
TS.CREATE ts:temperature:sensor1 RETENTION 0 LABELS type temperature unit celsius

# TS.ADD: adicionar ponto
TS.ADD ts:cpu:server1 * 45.5
TS.ADD ts:cpu:server1 1717000000000 67.2  # timestamp explícito

# TS.RANGE: consultar range
TS.RANGE ts:cpu:server1 - +               # todos
TS.RANGE ts:cpu:server1 - + COUNT 10      # últimos 10
TS.RANGE ts:cpu:server1 1717000000000 1717003600000

# TS.MRANGE: multi-range (por labels)
TS.MRANGE - + FILTER type=cpu

# Aggregation
TS.RANGE ts:cpu:server1 - +
    AGGREGATION avg 60000     # média a cada 60s
TS.RANGE ts:cpu:server1 - +
    AGGREGATION max 3600000   # máximo por hora

# TS.INFO: metadados
TS.INFO ts:cpu:server1

Retention e Compaction Rules

# Configurar retenção e regras de compactação

# Fonte: dados brutos com 1h de retenção
TS.CREATE ts:sensor:raw RETENTION 3600000  # 1h

# Regra: média por minuto, retenção 1 dia
TS.CREATE ts:sensor:1m RETENTION 86400000
TS.CREATERULE ts:sensor:raw ts:sensor:1m AGGREGATION avg 60000

# Regra: média por hora, retenção 30 dias
TS.CREATE ts:sensor:1h RETENTION 2592000000
TS.CREATERULE ts:sensor:1m ts:sensor:1h AGGREGATION avg 3600000

# Listar regras
TS.INFO ts:sensor:raw

RedisBloom

# BF (Bloom Filter): testa se elemento NÃO está no conjunto
# Falsos positivos possíveis, falsos negativos impossíveis

BF.ADD bloom:emails "[email protected]"
BF.EXISTS bloom:emails "[email protected]"          # 1
BF.EXISTS bloom:emails "[email protected]"       # 0 (definitivamente)
BF.RESERVE bloom:emails 0.01 1000000             # 1% erro, 1M elementos

# CMS (Count-Min Sketch): contagem aproximada de frequência
CMS.INCRBY cms:pageviews "home" 1
CMS.INCRBY cms:pageviews "products" 5
CMS.QUERY cms:pageviews "home"                    # ~1
CMS.MERGE cms:total cms:pageviews cms:pageviews2

# Top-K: manter K elementos mais frequentes
TOPK.ADD topk:products "notebook" 10
TOPK.ADD topk:products "mouse" 5
TOPK.ADD topk:products "monitor" 8
TOPK.LIST topk:products

# Count-min sketch com Top-K
TOPK.ADD topk:searches "redis" 100
TOPK.ADD topk:searches "cache" 80
TOPK.ADD topk:searches "database" 60
TOPK.QUERY topk:searches "cache"

RedisGraph (Legado)

# ⚠️ RedisGraph está em modo legacy/read-only desde 2024
# Não receberá novas features, apenas correções críticas
# Alternativas: use Neo4j, Apache AGE, ou memgraph

# Comandos (apenas referência)
GRAPH.QUERY social "CREATE (:Person {name: 'João', age: 30})"
GRAPH.QUERY social "MATCH (p:Person) RETURN p.name, p.age"
GRAPH.QUERY social "MATCH (a:Person)-[:KNOWS]->(b:Person) RETURN a.name, b.name"

Lab: Catálogo de Produtos com Busca

cat > catalog-search.sh << 'SCRIPT'
#!/bin/bash
set -euo pipefail

echo "=== CATÁLOGO DE PRODUTOS COM REDISEARCH ==="

# 1. Indexar
echo ""
echo "--- 1. Criando índice ---"
redis-cli << 'REDIS'
FT.CREATE idx_catalog ON JSON
    PREFIX 1 product:
    SCHEMA
        $.name AS name TEXT WEIGHT 3.0 SORTABLE
        $.description AS description TEXT
        $.category AS category TAG SORTABLE
        $.brand AS brand TAG
        $.price AS price NUMIC SORTABLE
        $.rating AS rating NUMIC
        $.tags[*] AS tags TAG
        $.in_stock AS in_stock TAG
REDIS

# 2. Popular
echo ""
echo "--- 2. Populando produtos ---"
redis-cli << 'REDIS'
JSON.SET product:1001 $ '{"name":"Notebook Dell XPS 15","description":"Notebook premium com Intel i7, 16GB RAM, 512GB SSD","category":"eletrônicos","brand":"Dell","price":8999.90,"rating":4.5,"tags":["notebook","premium","dell"],"in_stock":"true"}'

JSON.SET product:1002 $ '{"name":"Mouse Logitech MX Master 3","description":"Mouse ergonômico sem fio para produtividade","category":"periféricos","brand":"Logitech","price":499.90,"rating":4.8,"tags":["mouse","wireless","ergonômico"],"in_stock":"true"}'

JSON.SET product:1003 $ '{"name":"Monitor LG 27\" 4K","description":"Monitor 27 polegadas 4K IPS para design","category":"monitores","brand":"LG","price":2499.90,"rating":4.3,"tags":["monitor","4k","design"],"in_stock":"false"}'

JSON.SET product:1004 $ '{"name":"Teclado Mecânico Keychron K8","description":"Teclado mecânico Bluetooth/USB-C","category":"periféricos","brand":"Keychron","price":699.90,"rating":4.6,"tags":["teclado","mecânico","wireless"],"in_stock":"true"}'
REDIS

# 3. Buscas
echo ""
echo "--- 3. Buscas ---"
echo ">>> Produtos na categoria periféricos:"
redis-cli FT.SEARCH idx_catalog "@category:{periféricos}" RETURN 2 name price

echo ""
echo ">>> Produtos com preço entre 500 e 2500 disponíveis:"
redis-cli FT.SEARCH idx_catalog "@price:[500 2500] @in_stock:{true}" RETURN 2 name price

echo ""
echo ">>> Busca full-text por 'notebook':"
redis-cli FT.SEARCH idx_catalog "notebook" HIGHLIGHT TAGS "**" "**" RETURN 1 name

echo ""
echo ">>> Sugestões de autocomplete:"
redis-cli FT.SUGADD sug:products "notebook" 100
redis-cli FT.SUGADD sug:products "mouse" 90
redis-cli FT.SUGADD sug:products "monitor" 80
redis-cli FT.SUGADD sug:products "teclado" 70
redis-cli FT.SUGGET sug:products "not"
redis-cli FT.SUGGET sug:products "mou"

# 4. Agregado por categoria
echo ""
echo "--- 4. Agregação por categoria ---"
redis-cli FT.AGGREGATE idx_catalog "*"
    GROUPBY 1 @category
    REDUCE COUNT 0 AS total
    REDUCE AVG 1 @price AS avg_price
    REDUCE MAX 1 @rating AS max_rating
    SORTBY 2 @total DESC

# 5. Limpeza
echo ""
echo "--- 5. Limpeza ---"
redis-cli FT.DROPINDEX idx_catalog DD 2>/dev/null || true
redis-cli EVAL "return redis.call('DEL', unpack(redis.call('KEYS', 'product:*')))" 0 2>/dev/null || true
redis-cli EVAL "return redis.call('DEL', unpack(redis.call('KEYS', 'sug:*')))" 0 2>/dev/null || true
echo "Dados limpos."
SCRIPT

chmod +x catalog-search.sh

RedisJSON manipula documentos JSON nativamente com JSONPath. RediSearch indexa Hash e JSON com full-text, tags, numérico, geo. FT.AGGREGATE faz GROUP BY. RedisTimeSeries armazena e compacta métricas. RedisBloom oferece Bloom Filter, Count-Min Sketch e Top-K para aproximações probabilísticas.