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 problema: nem todo dado precisa de consultas

Muitos dados de um sistema são acessados sempre do mesmo jeito: pela chave. A sessão do usuário pelo identificador da sessão. O carrinho pelo usuário. O contador de requisições pelo cliente e pelo minuto. Para eles, as junções, os índices e as transações de um banco relacional são custo sem uso.

Um banco chave-valor guarda cada valor sob uma chave e o devolve pela mesma chave. O Redis, o mais usado, guarda tudo na memória e responde em frações de milissegundo. E vai além do chave-valor puro: o valor pode ser uma estrutura de dados com operações próprias.

O tópico de cache trata do Redis como cache. Este trata dele como banco: as estruturas, a atomicidade, o que trava o servidor e o que acontece com os dados numa queda. Os testes usaram o Redis 8.

As estruturas de dados

EstruturaO que guardaOperações típicasUsos
StringUm valor, inclusive númerosGET, SET, INCR, SET ... NX EXSessões, contadores, travas simples
HashCampos e valores sob uma chaveHSET, HGET, HINCRBYUm objeto: o carrinho, o perfil
ListUma sequênciaLPUSH, RPOP, LRANGEFilas simples, últimos itens
SetValores únicosSADD, SISMEMBER, SINTERMarcadores, quem já viu, interseções
Sorted setValores únicos ordenados por uma pontuaçãoZADD, ZINCRBY, ZRANGE, ZRANKPlacares, filas por prioridade, janelas de tempo
StreamUm log de eventos com consumidoresXADD, XREADGROUP, XACKFilas com confirmação, eventos
HyperLogLogUma contagem aproximada de distintosPFADD, PFCOUNTVisitantes únicos, com 12 KB por contador

A escolha da estrutura é a modelagem do Redis. Um placar como string serializada obriga a ler, ordenar e gravar a lista inteira a cada ponto. Como sorted set, cada ponto é um ZINCRBY, e os dez primeiros saem de um ZRANGE, já ordenados.

Atômico é o comando

O Redis executa um comando de cada vez, numa única thread. Cada comando é atômico: nada acontece no meio dele. Mas dois comandos seguidos não são. Medimos com 10 clientes fazendo 200 incrementos cada, num contador:

FormaValor final (o certo é 2.000)
GET, somar 1 na aplicação, SET353
INCR2.000

Com GET e SET, 82% dos incrementos se perderam: vários clientes leram o mesmo valor e gravaram o mesmo resultado. É a atualização perdida, de novo. O Redis tem um comando atômico para quase todo caso comum (INCR, HINCRBY, ZINCRBY, SET ... NX). Para lógicas que precisam de vários passos, há os scripts em Lua e as funções, que rodam inteiros sem interrupção.

Idas e voltas custam mais que o comando

Cada comando enviado e esperado é uma ida e volta pela rede. O pipelining manda vários comandos de uma vez e lê as respostas no fim. Gravando 20 mil chaves:

FormaComandos por segundo
Um comando por ida e volta13.148
Pipeline96.343

Mais de sete vezes mais rápido, na mesma rede Docker. Com o Redis em outra máquina, a diferença cresce, porque a ida e volta pesa mais. Pipeline não é transação: os comandos de outros clientes podem se intercalar entre os seus.

Um comando lento para o servidor inteiro

A mesma thread única que garante a atomicidade faz de cada comando lento uma pausa para todos. Com 1 milhão de chaves, medimos a pior espera de um cliente que fazia GET simples enquanto outro listava as chaves:

ComandoDuraçãoPior GET de outro cliente
KEYS sessao:*1.297 ms308 ms
SCAN sessao:*, em lotes de 1.0001.538 ms6,4 ms

O KEYS percorre todas as chaves num comando só, e o servidor para durante esse tempo. O SCAN faz o mesmo trabalho em pedaços, devolvendo o controle entre eles. O mesmo vale para outras operações sobre estruturas grandes: HGETALL num hash de um milhão de campos, LRANGE 0 -1 numa lista enorme, DEL de uma chave gigante (UNLINK apaga em segundo plano).

Memória é o limite

O Redis guarda tudo na memória, e o maxmemory define o teto. Quando ele chega, a política de despejo (maxmemory-policy) decide o que acontece. Testamos gravando 100 mil valores de 500 bytes com o limite em 20 MB:

PolíticaGravações aceitasChaves no fimA primeira chave gravada continua?
noeviction34.592, e 65.408 recusadas com erro34.592Sim
allkeys-lru100.00034.411Não

Com noeviction, o Redis recusa as gravações quando a memória acaba, com o erro command not allowed when used memory > 'maxmemory'. A aplicação fica sabendo. Com allkeys-lru, todas as gravações foram aceitas, e 65 mil chaves foram apagadas sem nenhum aviso para dar lugar às novas. Para um cache, é o comportamento desejado. Para um carrinho, uma sessão ou uma fila, é perda de dados silenciosa.

Persistência: o que sobra depois de uma queda

Por padrão, o Redis grava fotografias periódicas da memória em disco (RDB), por regras como "a cada 60 segundos, se houver 10 mil mudanças". Também pode gravar cada comando num log (AOF). Gravamos 10 mil chaves, confirmadas pelo servidor, e matamos o processo com kill -9 logo em seguida:

ConfiguraçãoChaves depois da quedaGravações por segundo (redis-benchmark, 50 conexões)
Padrão (só fotografias)0—
Sem persistência—117.371
AOF com appendfsync everysec10.000115.607
AOF com appendfsync always10.00021.349

Com o padrão, as 10 mil gravações confirmadas sumiram: nenhuma fotografia tinha sido tirada ainda. Com o AOF, todas sobreviveram. O kill -9 derruba o processo, mas não a máquina: o que o Redis já tinha escrito no arquivo continuava na memória do sistema operacional. Numa queda da máquina, que o teste não simulou, o everysec pode perder até cerca de um segundo de gravações, e o always não perde nenhuma, ao custo medido: cinco vezes menos gravações por segundo.

Por que isso importa quando a IA escreve o código

Um assistente que usa Redis costuma tratá-lo como um dicionário remoto:

  • GET, cálculo na aplicação e SET, em vez do comando atômico.
  • Um comando por ida e volta em laços, sem pipeline.
  • KEYS em código de produção para listar ou apagar chaves.
  • Estruturas inteiras serializadas em JSON numa string, em vez de hash, set ou sorted set.
  • Dados que não podem se perder num Redis com a configuração padrão, ou com allkeys-lru.
  • Chaves sem tempo de expiração, crescendo até a memória acabar.

O próximo nível compara os usos do Redis, as configurações de persistência e de memória, e as alternativas.

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.

  • Bancos chave-valor em cinco minutos

    Explique em no máximo cinco minutos de leitura o que é um banco chave-valor como o Redis, para alguém que só usou bancos relacionais.
    
    Cubra só o essencial: o modelo chave-valor, as estruturas de dados do Redis, por que os comandos são atômicos, o que acontece quando a memória acaba e o que se perde numa queda.
    
    Use exemplos de sessões, contadores e placares. No final, me faça três perguntas curtas para eu verificar se entendi, e corrija as minhas respostas.
  • As estruturas de dados do Redis

    Quero aprender a escolher a estrutura de dados certa no Redis.
    
    Aja como um mentor e, para cada estrutura (string, hash, list, set, sorted set, stream e HyperLogLog), uma de cada vez:
    1. Explique o que ela guarda e as operações principais, com o custo de cada uma.
    2. Me dê um problema e peça que eu escreva os comandos para resolvê-lo.
    3. Corrija a minha resposta e mostre um erro comum com essa estrutura.
    
    Espere a minha resposta em cada etapa.
  • A thread única do Redis

    Quero entender as consequências de o Redis executar os comandos numa única thread.
    
    Explique:
    1. Por que isso torna cada comando atômico.
    2. Por que dois comandos seguidos não são atômicos, com o exemplo de um contador.
    3. Por que um comando lento para todos os clientes, com KEYS, HGETALL e DEL de chaves grandes.
    4. Pipeline, MULTI/EXEC, WATCH e scripts em Lua: o que cada um garante.
    5. Como encontrar comandos lentos em produção (SLOWLOG, LATENCY).
    
    Depois me mostre quatro trechos de código e pergunte qual deles tem um problema de atomicidade ou de bloqueio.
  • Persistência e memória no Redis

    Quero entender o que acontece com os dados do Redis numa queda e quando a memória acaba.
    
    Explique:
    1. Fotografias (RDB) e log de comandos (AOF), e as opções de appendfsync.
    2. A diferença entre a queda do processo e a queda da máquina.
    3. As políticas de maxmemory e quando cada uma faz sentido.
    4. A replicação assíncrona e o que se perde quando o principal cai.
    5. Por que separar o Redis de cache do Redis de dados.
    
    Depois me descreva três usos e peça que eu escolha a persistência e a política de memória de cada um.