Bancos chave-valor
O modelo mais simples e mais rápido, com o Redis e suas estruturas de dados como referência.
O código desta página foi executado nas cinco linguagens contra um Redis 8.10. As medições do nível Conceito vêm do mesmo servidor.
Um placar com sorted set, em cada linguagem
Cada versão tem três funções: pontuar, com ZINCRBY; primeiros, com os N maiores e as pontuações; e posicao, com ZREVRANK. O programa dispara 1.000 pontuações simultâneas para cinco jogadores e consulta o placar.
import { createClient } from "redis";
const redis = await createClient({ url: process.env.REDIS_URL }).connect();
const PLACAR = "placar:semana";
// ZINCRBY é atômico: pontos simultâneos para o mesmo jogador nunca se perdem
const pontuar = (jogador: string, pontos: number) => redis.zIncrBy(PLACAR, pontos, jogador);
const primeiros = (n: number) => redis.zRangeWithScores(PLACAR, 0, n - 1, { REV: true });
// Posição de um jogador, contando do maior para o menor (0 é o primeiro)
const posicao = (jogador: string) => redis.zRevRank(PLACAR, jogador);
await redis.del(PLACAR);
await Promise.all(Array.from({ length: 1000 }, (_, i) => pontuar(`jogador${i % 5}`, (i % 10) + 1)));
console.log("três primeiros:", await primeiros(3));
console.log("posição do jogador0:", (await posicao("jogador0"))! + 1);
await redis.close();import os
from concurrent.futures import ThreadPoolExecutor
import redis
r = redis.Redis.from_url(os.environ["REDIS_URL"], decode_responses=True)
PLACAR = "placar:semana"
def pontuar(jogador: str, pontos: int) -> float:
# ZINCRBY é atômico: pontos simultâneos para o mesmo jogador nunca se perdem
return r.zincrby(PLACAR, pontos, jogador)
def primeiros(n: int) -> list[tuple[str, float]]:
return r.zrevrange(PLACAR, 0, n - 1, withscores=True)
def posicao(jogador: str) -> int | None:
return r.zrevrank(PLACAR, jogador) # 0 é o primeiro
r.delete(PLACAR)
with ThreadPoolExecutor(max_workers=20) as executor:
list(executor.map(lambda i: pontuar(f"jogador{i % 5}", i % 10 + 1), range(1000)))
print("três primeiros:", primeiros(3))
print("posição do jogador0:", posicao("jogador0") + 1)package main
import (
"context"
"fmt"
"os"
"sync"
"github.com/redis/go-redis/v9"
)
const placar = "placar:semana"
// pontuar usa ZINCRBY, atômico: pontos simultâneos nunca se perdem
func pontuar(ctx context.Context, r *redis.Client, jogador string, pontos float64) error {
return r.ZIncrBy(ctx, placar, pontos, jogador).Err()
}
func primeiros(ctx context.Context, r *redis.Client, n int64) ([]redis.Z, error) {
return r.ZRevRangeWithScores(ctx, placar, 0, n-1).Result()
}
// posicao conta do maior para o menor; 0 é o primeiro
func posicao(ctx context.Context, r *redis.Client, jogador string) (int64, error) {
return r.ZRevRank(ctx, placar, jogador).Result()
}
func main() {
ctx := context.Background()
opcoes, err := redis.ParseURL(os.Getenv("REDIS_URL"))
if err != nil {
panic(err)
}
r := redis.NewClient(opcoes)
defer r.Close()
r.Del(ctx, placar)
var wg sync.WaitGroup
for i := range 1000 {
wg.Go(func() { pontuar(ctx, r, fmt.Sprintf("jogador%d", i%5), float64(i%10+1)) })
}
wg.Wait()
top, _ := primeiros(ctx, r, 3)
fmt.Println("três primeiros:", top)
p, _ := posicao(ctx, r, "jogador0")
fmt.Println("posição do jogador0:", p+1)
}import java.util.List;
import java.util.concurrent.Executors;
import redis.clients.jedis.JedisPooled;
import redis.clients.jedis.resps.Tuple;
public class Placar {
static final String PLACAR = "placar:semana";
static final JedisPooled redis = new JedisPooled(System.getenv("REDIS_URL"));
// ZINCRBY é atômico: pontos simultâneos para o mesmo jogador nunca se perdem
static double pontuar(String jogador, double pontos) {
return redis.zincrby(PLACAR, pontos, jogador);
}
static List<Tuple> primeiros(int n) {
return redis.zrevrangeWithScores(PLACAR, 0, n - 1);
}
// Posição contando do maior para o menor; 0 é o primeiro
static Long posicao(String jogador) {
return redis.zrevrank(PLACAR, jogador);
}
public static void main(String[] args) {
redis.del(PLACAR);
try (var executor = Executors.newFixedThreadPool(20)) {
for (int i = 0; i < 1000; i++) {
int n = i;
executor.submit(() -> pontuar("jogador" + (n % 5), n % 10 + 1));
}
}
System.out.println("três primeiros: " + primeiros(3));
System.out.println("posição do jogador0: " + (posicao("jogador0") + 1));
}
}using StackExchange.Redis;
var url = Environment.GetEnvironmentVariable("REDIS_URL")!;
var conexao = await ConnectionMultiplexer.ConnectAsync(url);
var redis = conexao.GetDatabase();
const string Placar = "placar:semana";
// ZINCRBY é atômico: pontos simultâneos para o mesmo jogador nunca se perdem
Task<double> Pontuar(string jogador, double pontos) =>
redis.SortedSetIncrementAsync(Placar, jogador, pontos);
Task<SortedSetEntry[]> Primeiros(int n) =>
redis.SortedSetRangeByRankWithScoresAsync(Placar, 0, n - 1, Order.Descending);
// Posição contando do maior para o menor; 0 é o primeiro
Task<long?> Posicao(string jogador) =>
redis.SortedSetRankAsync(Placar, jogador, Order.Descending);
await redis.KeyDeleteAsync(Placar);
await Task.WhenAll(
Enumerable.Range(0, 1000).Select(i => Pontuar($"jogador{i % 5}", i % 10 + 1)));
Console.WriteLine("três primeiros: " + string.Join(", ", await Primeiros(3)));
Console.WriteLine($"posição do jogador0: {await Posicao("jogador0") + 1}");O resultado, nas cinco linguagens
O jogador i % 5 recebe i % 10 + 1 pontos, para i de 0 a 999. O placar esperado é jogador4 com 1.500, jogador3 com 1.300 e jogador2 com 1.100, e o jogador0, com 700, em quinto.
| Cliente | Três primeiros | Posição do jogador0 | |
|---|---|---|---|
| TypeScript | redis 6.3 | 1.500, 1.300, 1.100 | 5 |
| Python | redis 8.1 | 1.500, 1.300, 1.100 | 5 |
| Go | go-redis 9.23 | 1.500, 1.300, 1.100 | 5 |
| Java | Jedis 5.2 | 1.500, 1.300, 1.100 | 5 |
| C# | StackExchange.Redis 3.4 | 1.500, 1.300, 1.100 | 5 |
Nenhum ponto se perdeu nas mil pontuações simultâneas, porque cada ZINCRBY é atômico no servidor. A mesma lógica com "ler o placar, somar na aplicação e gravar" perderia pontos, como o contador do nível Conceito, que chegou a 353 em vez de 2.000.
O que muda de uma linguagem para outra
| Incrementar | Os N maiores | Posição | |
|---|---|---|---|
| TypeScript | zIncrBy | zRangeWithScores(..., { REV: true }) | zRevRank |
| Python | zincrby | zrevrange(..., withscores=True) | zrevrank |
| Go | ZIncrBy | ZRevRangeWithScores | ZRevRank |
| Java | zincrby | zrevrangeWithScores | zrevrank |
| C# | SortedSetIncrementAsync | SortedSetRangeByRankWithScoresAsync(..., Order.Descending) | SortedSetRankAsync(..., Order.Descending) |
Repare na ordem dos argumentos: o Redis recebe ZINCRBY chave incremento membro, e a maioria dos clientes segue essa ordem, mas o StackExchange.Redis recebe o membro antes do incremento. O StackExchange.Redis também usa um único ConnectionMultiplexer compartilhado por toda a aplicação, e os outros clientes usam um pool de conexões.
Num placar real, a chave leva o período (placar:2026-46) e uma expiração, para os placares antigos saírem sozinhos da memória.
A configuração do servidor
As escolhas do nível Soluções, num redis.conf para dados que não são só cache:
appendonly yes
appendfsync everysec # medido: 116 mil gravações/s, contra 117 mil sem AOF
maxmemory 2gb
maxmemory-policy noeviction # recusa gravações com a memória cheia, em vez de apagar chavesE para um Redis só de cache:
appendonly no
maxmemory 2gb
maxmemory-policy allkeys-lruAs linhas appendfsync e maxmemory-policy foram medidas no teste, passadas como opções na linha de comando do redis-server. O arquivo completo não foi usado como está.
Liberar uma trava sem apagar a de outro
O script em Lua confere o dono antes de apagar, de forma atômica:
-- KEYS[1]: a chave da trava; ARGV[1]: o identificador aleatório de quem a pegou
if redis.call("GET", KEYS[1]) == ARGV[1] then
return redis.call("DEL", KEYS[1])
end
return 0No teste, com a trava pertencendo ao processo B, o processo A tentou liberá-la e o script devolveu 0, sem apagar nada. Em seguida, B a liberou, e o script devolveu 1.
Lista de revisão
Modelagem
- O Redis guarda só o que é acessado pela chave e cabe na memória?
- Cada dado usa a estrutura certa (hash, set, sorted set, stream), e não JSON numa string?
- As chaves têm prefixos e, as temporárias, expiração?
Operações
- Incrementos e alterações condicionais usam comandos atômicos ou scripts?
- Laços de comandos usam pipeline?
- O código usa
SCANem vez deKEYS, eUNLINKpara chaves grandes? - Travas guardam um identificador do dono e são liberadas com conferência?
Configuração
- A persistência foi escolhida pelo uso, e não deixada no padrão?
- A política de memória combina com o uso: despejo para cache, recusa para dados?
- Cache e dados ficam em instâncias separadas?
- Há réplica, se o Redis cair, e alguém sabe o que se perde nesse caso?
Prompts para aprofundar
Ao abrir no ChatGPT ou no Claude, o prompt é enviado ao serviço escolhido pela URL e também fica copiado na sua área de transferência.
Implementar com o Redis do jeito certo
Quero implementar uma funcionalidade com o Redis (um placar, um contador, um carrinho ou sessões). Pergunte qual linguagem e cliente eu uso, e qual é a funcionalidade. Vamos em etapas: 1. A estrutura de dados e o formato das chaves, com expiração. 2. As operações com comandos atômicos, sem ler e gravar na aplicação. 3. Pipeline onde houver muitos comandos. 4. Um teste com operações simultâneas que prove que nada se perde. 5. A configuração de persistência e memória para esse uso.
Investigar lentidão no Redis
Meu Redis está com picos de latência. Pergunte qual versão e serviço eu uso, como os clientes acessam e o que as métricas mostram (memória, conexões, comandos por segundo). Me guie: 1. SLOWLOG e LATENCY para achar os comandos lentos. 2. Encontrar chaves grandes (redis-cli --bigkeys, MEMORY USAGE). 3. Uso de KEYS, HGETALL ou LRANGE completos no código. 4. Persistência e fork pesando no servidor. 5. As correções para cada causa.
Revisar o uso do Redis
Vou colar abaixo o código que usa o Redis numa aplicação, e a configuração do servidor, se houver. Revise para produção. Verifique, e para cada item diga se está correto, incorreto ou ausente, citando o trecho: 1. Há ler, calcular e gravar em vez de comandos atômicos? 2. Há KEYS, ou comandos sobre estruturas enormes? 3. Laços de comandos usam pipeline? 4. As chaves temporárias têm expiração? 5. Travas guardam o dono e são liberadas com conferência? 6. A persistência e a política de memória combinam com os dados guardados? Para cada problema, descreva o efeito em produção e mostre a correção. Código e configuração: [cole aqui]