.env.
Lição 0: do zero ao primeiro comando (15 min)
Bloom: esse baú já foi aberto?¶
"Esse item já foi visto?"; responder isso milhões de vezes sem guardar a lista inteira em memória. Um Bloom filter responde com alguns KB: "não" é certeza absoluta; "sim" é "provavelmente", com uma taxa de falso positivo que você escolhe. É uma estrutura probabilística que vem no Redis 8 e no Redis Cloud, com comandos BF.*.
O que o lab faz¶
- Reservar um filtro para 1000 baús com 1% de erro (
BF.RESERVE) - Marcar baús abertos com
BF.ADDeBF.MADD - Perguntar
BF.EXISTSpara um baú aberto e para um nunca aberto - Testar 1000 baús que ninguém abriu e contar os falsos positivos
- Comparar a memória do filtro com a de um
SETque guarda os mesmos ids
Faça agora¶
./quest run 101-06 jedis
./quest run 101-06 lettuce # opcional: mesmo lab, outro client
./quest verify 101-06
O código¶
String chests = ctx.k("chests", "opened");
try (RedisClient jedis = Clients.jedis()) {
jedis.unlink(chests);
jedis.bfReserve(chests, 0.01, 1000); // 1% error, room for 1000 chests
boolean fresh = jedis.bfAdd(chests, "chest-001"); // true: some bit was off
boolean again = jedis.bfAdd(chests, "chest-001"); // false: all bits already on
List<Boolean> added = jedis.bfMAdd(chests, "chest-002", "chest-003", "chest-004");
boolean opened = jedis.bfExists(chests, "chest-001"); // true: probably opened
boolean never = jedis.bfExists(chests, "chest-777"); // false: certainly not
jedis.bfMAdd(chests, ids); // ids = chest-001 .. chest-1000, 250 per call
List<Boolean> answers = jedis.bfMExists(chests, neverOpened); // chest-5001 .. chest-6000
int falsePositives = countTrue(answers); // every true here is a false positive
long card = jedis.bfCard(chests); // estimated items
Map<String, Object> info = jedis.bfInfo(chests); // Capacity, Size, Number of filters...
Long bytes = jedis.memoryUsage(chests);
}
try (StatefulRedisConnection<String, String> connection = Clients.lettuceConnection()) {
RedisCommands<String, String> redis = connection.sync();
redis.unlink(chests);
redis.bfReserve(chests, 0.01, 1000);
Boolean fresh = redis.bfAdd(chests, "chest-001");
Boolean again = redis.bfAdd(chests, "chest-001");
List<Boolean> added = redis.bfMAdd(chests, "chest-002", "chest-003", "chest-004");
Boolean opened = redis.bfExists(chests, "chest-001");
Boolean never = redis.bfExists(chests, "chest-777");
redis.bfMAdd(chests, ids);
List<Boolean> answers = redis.bfMExists(chests, neverOpened);
int falsePositives = countTrue(answers);
Long card = redis.bfCard(chests);
BfInfoValue info = redis.bfInfo(chests); // getCapacity(), getSize(), getNumberOfFilters()...
Long bytes = redis.memoryUsage(chests);
}
No Redis Insight¶
No Browser, seu-prefixo:chests:opened aparece com o tipo do módulo (MBbloom--) e sem conteúdo navegável: um filtro não guarda os ids, só bits. Ao lado, seu-prefixo:chests:opened:plain é o SET com os mesmos 1000 ids; compare o tamanho das duas chaves na coluna de memória.
No Workbench, rode BF.INFO seu-prefixo:chests:opened e depois BF.EXISTS seu-prefixo:chests:opened chest-5042. Se vier 1, você encontrou um dos falsos positivos do lab; troque o número e tente de novo.
Por dentro
| Comando | O que faz |
|---|---|
BF.RESERVE key erro capacidade |
Cria o filtro dimensionado para a taxa de erro e a quantidade de itens esperada |
BF.ADD key item |
Marca um item. Devolve 1 se algum bit mudou, 0 se todos já estavam acesos |
BF.MADD key item [item ...] |
Vários itens em uma viagem |
BF.EXISTS key item |
0: com certeza não está. 1: provavelmente está |
BF.MEXISTS key item [item ...] |
Vários itens em uma viagem |
BF.INSERT key ITEMS item ... |
Cria o filtro (se não existe) e adiciona, no mesmo comando |
BF.CARD key |
Estimativa de quantos itens entraram |
BF.INFO key |
Capacidade, tamanho em bytes, número de sub-filtros, itens inseridos, fator de expansão |
Como funciona: cada item passa por k funções de hash e acende k bits de um vetor. Consultar é conferir se os k bits estão acesos. Bits são compartilhados entre itens, daí o falso positivo; nenhum bit é apagado, daí o zero falso negativo.
Quanto custa a certeza
O lab cria um SET com os mesmos 1000 ids e mede os dois com MEMORY USAGE. O filtro fica perto de 1 KB; o SET, dezenas de KB. A conta muda de patamar com milhões de baús por jogador.
Em produção
- Dimensione a capacidade para o crescimento real. Passou da capacidade, o Redis cria sub-filtros (o fator
expansion), e cada consulta passa a olhar todos eles: mais lento e menos preciso. UmBF.RESERVEgeneroso custa poucos KB. - Não dá para remover um item de um Bloom filter. Se precisa de remoção, use o Cuckoo filter (
CF.*). Se precisa listar os itens, guarde a fonte da verdade em outro lugar (um SET, um Hash, o banco) e use o filtro como pré-checagem barata. - Casos clássicos: "esse usuário já viu esse item?", "esse e-mail já foi cadastrado?", deduplicação de eventos antes de gravar. Sempre onde um "talvez sim" seguido de uma checagem cara é aceitável, e um "não" barato evita a maioria das checagens.
Desafio
Troque ERROR_RATE para 0.001 (0,1%) no lab e rode de novo: os falsos positivos caem para perto de 1 em 1000 e o Size do BF.INFO cresce cerca de 50%. Depois experimente 0.1 e veja o efeito contrário.