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.