DevGuideTK
← Trilha Backend

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();

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.

ClienteTrês primeirosPosição do jogador0
TypeScriptredis 6.31.500, 1.300, 1.1005
Pythonredis 8.11.500, 1.300, 1.1005
Gogo-redis 9.231.500, 1.300, 1.1005
JavaJedis 5.21.500, 1.300, 1.1005
C#StackExchange.Redis 3.41.500, 1.300, 1.1005

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

IncrementarOs N maioresPosição
TypeScriptzIncrByzRangeWithScores(..., { REV: true })zRevRank
Pythonzincrbyzrevrange(..., withscores=True)zrevrank
GoZIncrByZRevRangeWithScoresZRevRank
JavazincrbyzrevrangeWithScoreszrevrank
C#SortedSetIncrementAsyncSortedSetRangeByRankWithScoresAsync(..., 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 chaves

E para um Redis só de cache:

appendonly no
maxmemory 2gb
maxmemory-policy allkeys-lru

As 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 0

No 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 SCAN em vez de KEYS, e UNLINK para 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]