Entendiendo los filtros Bloom con ejemplos
(llimllib.github.io)- Un filtro Bloom es una estructura de datos probabilística que permite verificar rápidamente si un elemento pertenece a un conjunto usando la memoria de forma eficiente
- Solo indica si un elemento definitivamente no está en el conjunto o si podría estar, y existe una probabilidad de falsos positivos
- Su estructura básica usa un vector de bits y varias funciones hash para establecer en 1 los bits correspondientes a cada elemento
- La tasa de error y el rendimiento dependen del tamaño del filtro y de la cantidad de funciones hash, por lo que pueden ajustarse según el caso de uso
- También se presentan funciones hash recomendadas, métodos de configuración óptima, eficiencia de espacio y casos de uso reales
¿Qué es un filtro Bloom?
- Un filtro Bloom es una estructura de datos que permite determinar de forma rápida y eficiente en memoria si un elemento existe en un conjunto
- Para lograr esta eficiencia, el filtro Bloom es una estructura de datos probabilística, por lo que el resultado de la consulta se divide entre "definitivamente no está" en el conjunto o "podría estar"
- La estructura central de un filtro Bloom es un vector de bits
- Al agregar un elemento, este se hashea varias veces y los bits en esos índices se establecen en 1
- Si todos los bits correspondientes a los índices producidos por cada función hash están en 1, se considera que "podría existir"; de lo contrario, se trata como "definitivamente no está"
Ejemplo de cómo funciona
- Varias funciones hash (por ejemplo, Fnv, Murmur) mapean un elemento a múltiples índices de bits
- Al agregar un elemento, los bits de los índices calculados cambian a 1
- Al comprobar si un elemento existe, si todos los índices calculados con las mismas funciones hash y del mismo modo están en 1, se considera que "podría existir"
- Si aunque sea uno de esos bits está en 0, puede determinarse con certeza que "no está en el conjunto"
- Debido a esto, existe la posibilidad de falsos positivos
Temas avanzados
Atención: el autor no tiene experiencia aplicando filtros Bloom en servicios reales a gran escala
Selección de funciones hash
- Se recomiendan funciones hash independientes y con distribución uniforme
- Las funciones hash criptográficas (
sha1, etc.) no son adecuadas porque son lentas - Ejemplos de funciones hash rápidas y simples: Murmur, xxHash, Fnv, HashMix, etc.
- En un caso real, cambiar de md5 a murmur produjo una mejora de velocidad superior al 800%
Cómo determinar el tamaño del filtro Bloom
- Cuanto mayor sea el tamaño del filtro (m), menor será la tasa de falsos positivos
- La tasa de falsos positivos suele aproximarse con (1-e^(-kn/m))^k
- Hay que definir adecuadamente la cantidad esperada de elementos (n), el tamaño del filtro (m) y la cantidad de funciones hash (k)
¿Cuántas funciones hash usar?
- Cuantas más funciones hash haya, más lenta será la operación y más rápido se llenará el filtro
- Si hay muy pocas, aumenta la tasa de falsos positivos
- El valor ideal de k se calcula como (m/n)ln(2)
- Al diseñarlo, se sigue normalmente este proceso:
- Estimar la cantidad esperada de elementos n
- Definir la cantidad de bits m
- Calcular el k óptimo
- Verificar si se obtiene la tasa de error deseada; si no, ajustar m
Rendimiento y eficiencia de espacio
- En un filtro Bloom, agregar elementos/verificar existencia tiene complejidad temporal O(k)
- La eficiencia de espacio depende del margen de error aceptable y del rango de elementos
- Si no es posible predecir ni siquiera de manera aproximada el rango de elementos, puede convenir más una tabla hash o un filtro Bloom escalable
Casos de uso
- Para ejemplos de uso más detallados, consulta Wikipedia
- C. Titus Brown presenta un caso de aplicación de filtros Bloom en bioinformática
Material de referencia
- Broder, Mitzenmacher : Network Applications of Bloom Filters: A Survey — artículo de referencia sobre filtros Bloom
- Wikipedia – Bloom Filter
- Kirsch, Mitzenmacher: Less Hashing, Same Performance
- Almeida y otros: Scalable Bloom Filters
1 comentarios
Comentarios en Hacker News
"bloom"y"demonstrators "(incluyendo el espacio final) colisionan en fnv: 7, murmur: 12insert; pero cuando sí encaja, reduce muchísimo la carga de trabajoquerySelector(), en prefiltros de diccionarios de búsqueda por hash en buckets de CSS, y en rechazos rápidos al buscar atributos Aria para accesibilidad. Los filtros pequeños de 32 y 64 bits sí funcionan bien en la práctica. También se usan Bloom filters más grandes cuando corresponde; yo mismo agregué funciones de ese tiposstablepara keys que en realidad no existían. Tardé en entender qué significaban los Bloom filters adjuntos a cadasstable. La tasa predeterminada de falsos positivos era 0.1, demasiado alta para nuestro caso. Casi todo eran cache misses, y por culpa de esos falsos positivos había demasiadas consultas inútiles. Al bajarla a 0.01, el uso de memoria subió un poco, pero las lecturas innecesarias cayeron bastante y la latencia de lectura p99 bajó entre 16% y 18%