Entendendo filtros de Bloom com exemplos
(llimllib.github.io)- Filtro de Bloom é uma estrutura de dados probabilística que verifica rapidamente se um elemento está em um conjunto de forma eficiente em memória
- Ele só informa se o elemento definitivamente não está no conjunto ou se talvez esteja, com possibilidade de falso positivo
- A estrutura básica usa um vetor de bits e várias funções de hash para definir como 1 os bits correspondentes a cada elemento
- Taxa de erro e desempenho são determinados pelo tamanho do filtro e pela quantidade de funções de hash, podendo ser ajustados conforme o uso
- Também são apresentados funções de hash recomendadas, métodos de configuração ideal, eficiência de espaço e casos reais de aplicação
O que é um filtro de Bloom
- Filtro de Bloom é uma estrutura de dados para determinar de forma rápida e eficiente em memória se um determinado elemento existe em um conjunto
- Para obter essa eficiência, o filtro de Bloom é uma estrutura probabilística, e o resultado da verificação se divide entre "definitivamente não está no conjunto" ou "pode estar no conjunto"
- A estrutura central do filtro de Bloom é um vetor de bits
- Ao adicionar um elemento, ele é processado por hash várias vezes, e os bits nesses índices são definidos como 1
- Se os bits correspondentes aos índices produzidos por cada função de hash forem todos 1, considera-se que o elemento "pode existir"; caso contrário, ele é tratado como "definitivamente não existe"
Exemplo do funcionamento
- Várias funções de hash (ex.: Fnv, Murmur) mapeiam um elemento para vários índices de bits
- Ao adicionar um elemento, os bits nos índices calculados são alterados para 1
- Ao verificar a existência de um elemento específico, se os índices calculados da mesma forma com as mesmas funções de hash forem todos 1, considera-se que ele "pode existir"
- Se ao menos um desses bits for 0, é possível afirmar com certeza que ele "não está no conjunto"
- Por isso, existe a possibilidade de falso positivo
Tópicos avançados
Atenção: o autor não tem experiência prática de aplicar filtros de Bloom em serviços de grande escala
Escolha da função de hash
- Recomenda-se usar funções de hash independentes e com distribuição uniforme
- Funções de hash criptográficas (como sha1) não são adequadas por serem lentas
- Exemplos de funções de hash rápidas e simples: Murmur, xxHash, Fnv, HashMix etc.
- Em um caso real, a troca de md5 por murmur resultou em um ganho de velocidade superior a 800%
Como definir o tamanho do filtro de Bloom
- Quanto maior o tamanho do filtro (m), menor a taxa de falso positivo
- A taxa de falso positivo geralmente pode ser aproximada por (1-e^(-kn/m))^k
- É preciso definir adequadamente a quantidade esperada de elementos (n), o tamanho do filtro (m) e o número de funções de hash (k)
Quantas funções de hash?
- Quanto mais funções de hash, mais lenta fica a operação e mais rápido o filtro se enche
- Se forem poucas demais, a taxa de falso positivo aumenta
- O valor ideal de k é calculado como (m/n)ln(2)
- No projeto, o processo é o seguinte:
- Estimar a quantidade esperada de elementos n
- Definir o número de bits m
- Calcular o k ideal
- Verificar se a taxa de erro desejada foi atingida; se não, ajustar m
Desempenho e eficiência de espaço
- No filtro de Bloom, adição de elementos/verificação de existência têm complexidade de tempo O(k)
- A eficiência de espaço é determinada pela faixa de erro aceitável e pelo intervalo de elementos
- Se não for possível prever nem aproximadamente o intervalo de elementos, uma tabela hash ou um filtro de Bloom escalável pode ser uma opção melhor
Casos de uso
- Para exemplos detalhados de uso, consulte Wikipedia
- C. Titus Brown apresenta casos de aplicação de filtros de Bloom em bioinformática
Materiais de referência
- Broder, Mitzenmacher : Network Applications of Bloom Filters: A Survey — artigo de visão geral sobre filtros de Bloom
- Wikipedia – Bloom Filter
- Kirsch, Mitzenmacher: Less Hashing, Same Performance
- Almeida et al.: Scalable Bloom Filters
1 comentários
Comentários no Hacker News
"bloom"e"demonstrators "(incluindo o espaço final) colidem em fnv: 7, murmur: 12setque pode ter poucos elementos, mas no qual a checagem de pertinência é frequente, coloco junto um Bloom filter de 64 bits com uma função de hash bem simples. Parece bobo, mas o custo é tão baixo que dá para usar quase como uma aposta. Se não funcionar, só adiciona algo como 10ns à checagem de pertinência ou à inserção; se funcionar bem, pode reduzir enormemente a quantidade de trabalhorapidhashe esses microfiltros. Exemplos:querySelector(), pré-filtro de dicionário para lookup de hash em buckets de CSS, rejeição rápida na busca por atributos Aria para acessibilidade. Filtros pequenos de 32 e 64 bits também funcionam bem na prática. Bloom filters maiores também são usados adequadamente, e eu mesmo adicionei esse tipo de recursosete tabela hash se sobrepõem.seté uma tabela hash em que só importam as chaves, e Bloom filter é umsetque explora ativamente a característica de colisão do hashing. Usa funções de hash feitas para provocar colisões com facilidade. Se uma determinada chave já foi inserida, ela sempre vai bater. Mas pode existir outra chave com o mesmo valor de hash. Então isso não é um bug, e sim uma característica intencionalsetquando as 3 batem. Essa estrutura reduz a probabilidade de falso positivo, enquanto falso negativo nunca acontecesstablepara chaves que nem existiam. Só depois entendi o papel do Bloom filter anexado a cadasstable. A taxa padrão de falso positivo era 0.1, alta demais para o nosso caso. Quase tudo era cache miss, e os falsos positivos geravam muitas consultas inúteis. Ao baixar a taxa para 0.01, o uso de memória aumentou um pouco, mas as leituras desnecessárias caíram bastante, e a latência de leitura p99 melhorou de 16% a 18%