Entender los filtros Bloom con ejemplos
(llimllib.github.io)- Un filtro Bloom es una estructura de datos que permite filtrar rápidamente si algo pertenece a un conjunto grande usando poca memoria, y solo distingue entre “definitivamente no está” y “podría estar”
- La clave está en un vector de bits y varias funciones hash; al insertar, se ponen en 1 los bits en las posiciones apuntadas por los resultados hash
- Al consultar, si alguna de esas posiciones sigue en 0, el elemento puede descartarse, pero aunque todas estén en 1, todavía existe la posibilidad de un falso positivo
- Las funciones hash deben ser independientes, distribuirse de forma cercana a uniforme y además ser rápidas; hay un caso en que cambiar de md5 a murmur dio una mejora de velocidad de aproximadamente 800%
- La precisión y el costo del filtro dependen del equilibrio entre la cantidad esperada de elementos n, el número de bits m y la cantidad de hashes k; tanto la inserción como la consulta son de orden O(k)
Cómo funciona un filtro Bloom
- Un filtro Bloom es una estructura de datos probabilística para determinar de forma rápida y eficiente en memoria si un elemento pertenece a un conjunto
- El resultado se limita a dos posibilidades
- el elemento definitivamente no está en el conjunto
- el elemento podría estar en el conjunto
- Su estructura interna es un vector de bits; al agregar un elemento, la entrada se pasa por varias funciones hash
- Luego se ponen en 1 los bits en los índices señalados por cada valor hash y así termina la inserción
- En el ejemplo se usan Fnv y Murmur como funciones hash simples
Verificación de pertenencia y falsos positivos
- La consulta usa las mismas funciones hash que se usaron al insertar
- Si al menos uno de los bits apuntados por los valores hash está en 0, ese elemento definitivamente no está en el conjunto
- Si todos los bits relacionados están en 1, el elemento podría estar
- esos mismos bits podrían haber sido activados antes por otro elemento o por la combinación de varios elementos
- Debido a estas colisiones, en los filtros Bloom existe la posibilidad de falsos positivos (false positives)
Criterios para elegir funciones hash
- Las funciones hash de un filtro Bloom deben ser independientes, distribuirse de forma cercana a uniforme y ser lo más rápidas posible
- Los hashes criptográficos como sha1 se usan ampliamente, pero no siempre son una buena elección para un filtro Bloom
- Algunos ejemplos de hashes rápidos y simples son:
- Hay un caso en el que cambiar una implementación de filtro Bloom de md5 a murmur produjo una mejora de velocidad de aproximadamente 800%
Hashes usados en implementaciones reales
- Distintas implementaciones usan diferentes funciones hash en sus filtros Bloom
- Chromium: usa murmur
- Plan9: usa un hash simple propuesto en Mitzenmacher 2005
- Sdroege Bloom filter: usa fnv1a
- Squid: usa MD5
- RedisBloom: usa murmur
- Apache Spark: usa murmur
- influxdb: usa xxhash
- bloomd: los dos primeros hashes usan murmur, los dos siguientes usan SpookyHash, y los posteriores usan una combinación de ambos
- fleur, flor, bloom: usan fnv
- Sqlite: agregó un filtro Bloom para consultas analíticas
- RocksDB: es configurable, y en el código se indica que xxh3, de la familia xxhash, dio el mejor resultado
- ScyllaDB: usa murmur
Cómo definir el tamaño del filtro y la cantidad de funciones hash
- Un filtro Bloom permite ajustar la tasa de falsos positivos
- un filtro más grande reduce los falsos positivos
- un filtro más pequeño aumenta los falsos positivos
- La tasa de falsos positivos se calcula aproximadamente como
(1-e^-kn/m)^k- n: cantidad esperada de elementos a insertar
- m: cantidad de bits del filtro
- k: cantidad de funciones hash
- Cuantas más funciones hash haya, más lentas serán la consulta y la inserción, y además el filtro se llenará más rápido
- En cambio, si hay muy pocas funciones hash, los falsos positivos pueden volverse excesivos
- Para valores dados de m y n, el valor óptimo de k puede elegirse como
(m/n)ln(2) - El tamaño del filtro puede ajustarse con este proceso
- estimar aproximadamente el valor esperado de n
- elegir un valor de m
- calcular el valor óptimo de k
- calcular la tasa de error con los valores elegidos de n, m y k
- si la tasa de error no es aceptable, cambiar m y volver a calcular
Rendimiento y condiciones de uso adecuadas
- En un filtro Bloom con m bits y k funciones hash, tanto la inserción como la verificación de pertenencia son de orden O(k)
- Al agregar o consultar un elemento, basta con pasarlo por k funciones hash y luego activar o revisar los bits correspondientes
- La eficiencia de espacio depende de la tasa de error que se pueda tolerar
- Si el rango de elementos que pueden insertarse es muy limitado, un vector de bits determinista puede ser mejor opción
- Si no es posible estimar ni siquiera de forma aproximada cuántos elementos se insertarán, una tabla hash o un scalable Bloom filter puede ser más adecuada
Material de referencia y ejemplos de uso
- Se pueden ver ejemplos de uso de filtros Bloom en los ejemplos de Bloom filter en Wikipedia
- La charla de C. Titus Brown trata casos de uso de filtros Bloom en bioinformática
- Principales referencias
1 comentarios
Opiniones en Hacker News
Este artículo está dirigido justo a alguien como yo. Había oído el nombre filtro de Bloom y, cada vez que lo mencionaban, pensaba que tenía que investigarlo; al ver este artículo por fin lo hice, y resultó ser la introducción perfecta que quería :)
En 2009, en la universidad, hice un filtro de Bloom con CUDA, y mi asesor había trabajado antes en Nvidia. Pero después, en mi carrera, nunca volví a hacer programación en GPU.
Si hubiera tomado otras decisiones en ese momento, quizá habría ganado 100 millones de dólares.
Luego me fui a hacer otra cosa y me perdí una fortuna.
Hace 10 años hice una implementación de hashcash en GPU, pero hoy casi no debe tener valor.
Nota para el autor: la parte interactiva está muy buena. Para mostrar la idea central con más claridad, estaría bueno dar un ejemplo de dos cadenas que tengan una colisión de hash, poner una en el primer campo de entrada y luego verificar la otra en el segundo.
Así se puede mostrar por qué la respuesta siempre es “podría estar en el conjunto” y no “está”.
"bloom"y"demonstrators "colisionan. Hay que prestar atención al espacio al final de la segunda cadena.Ambas colisionan con
fnv: 7,murmur: 12.Tengo un truco que me gusta. Si a veces tienes que hacer muchas verificaciones de membresía en un conjunto que podría ser pequeño, puedes agregar de forma especulativa un filtro de Bloom de 64 bits con una función hash muy simple.
Suena tremendamente tonto, pero el costo es tan bajo que vale la pena intentarlo como una apuesta. Si no funciona bien, apenas agrega unos 10 ns a las inserciones y a las verificaciones de membresía; si funciona, puede reducir una cantidad enorme de trabajo.
Por ejemplo, se usan en ciertos casos de
querySelector(), para prefiltrar búsquedas hash en buckets de CSS y para rechazar rápidamente elementos al buscar ciertos atributos Aria para accesibilidad. Sorprende que filtros diminutos de 32 o 64 bits funcionen, pero en la práctica suelen ser efectivos. También hay algunos filtros de Bloom más grandes. Varios de estos los agregué yo.Le pedí a ChatGPT que hiciera uno en Python, y usó el enfoque de cortar el digest md5 básico para usarlo como si fueran varios hashes. Para usos no críticos me parece bien.
Otra visualización de los filtros de Bloom se puede ver al final de esta página:
https://www.chrislaux.com/hashtable.html
Pensé que ya lo había visto antes, pero en realidad era esta otra página: https://bdupras.github.io/filter-tutorial/
Esta página agrega un poco más de información al comparar filtros de Bloom y filtros cuckoo.
Hace poco implementé con un filtro de Bloom una función para evitar spam en mensajes de log. En el logger, hasheaba el mensaje, lo metía en el filtro y, si el elemento ya estaba, no imprimía el mensaje.
Cada pocos segundos recorría el filtro y limpiaba todos los bits; encajó bien porque no tenía que preocuparme por limpiar atómicamente todos los bits del filtro. Si mientras llegaban mensajes se borraban algunos de esos bits, eso ya era suficiente para que volvieran a aparecer en el log. La implementación anterior contaba la cantidad de mensajes vistos y se saturaba en N, lo que hacía que, si cierto mensaje se repetía, solo se viera por debajo de la velocidad a la que se limpiaba el filtro.
Fue bastante satisfactorio haber conocido los filtros de Bloom y encontrarles así, de manera natural, un uso real que produjo una gran mejora.
Si quieres leer más, también me gustó el artículo de Eli Bendersky sobre filtros de Bloom:
https://eli.thegreenplace.net/2025/bloom-filters/
Intuitivamente, los conceptos necesarios para entender filtros de Bloom, conjuntos y tablas hash se superponen como en un 95%. Un conjunto es una tabla hash para pruebas de membresía que solo se preocupa por claves, no por valores, y un filtro de Bloom es un conjunto que aprovecha el hecho de que el hashing muchos-a-uno “comprime” el espacio de claves mediante colisiones.
Es como usar deliberadamente una función hash que produce muchas colisiones. Si una clave específica se hasheó alguna vez, necesariamente dará positivo, pero también podría existir otra clave que haya producido el mismo hash. No es un bug, es una característica.
Esto reduce la probabilidad de colisiones de falsos positivos y mantiene la garantía de que no haya falsos negativos.