Skip to main content

Command Palette

Search for a command to run...

Notes: Bloom Filters - Implementation and Real World Applications

Updated
3 min read
Notes: Bloom Filters - Implementation and Real World Applications

Bloom filters are probabilistic data structures used to test whether an element is possibly in a set or definitely not in a set. These data structures are a neat hack to determine if an element is in a set without actually storing the actual data.

A bloom filter is simply an array of 1s and 0s filled using a hash function. They are way more memory efficient and have faster lookups than actual data. For more theory, refer this tutorial: Bloom Filters

In this blog, we’ll cover some implementations keeping these in mind:

  • Memory is limited

  • False positives are acceptable

  • False negatives are not

Checking Web Cache

Let’s say we have a distributed system where we store static html page caches in redis key value. With hundreds of thousands of clients, we may have millions or even billions of keys. If we get a request to get a static page, going through the whole cache list itself would be compute heavy and with millions of requests this would either cause slowness and failures or we’ll have to invest more and more dollars to scale our system. Both of these are unwanted.

So, we’ll try to build a solution using bloom filters that quickly tells us whether the url is in cache or not without going through the whole list and route the request accordingly.

package main

import (
    "context"
    "fmt"
    "log"

    "github.com/bits-and-blooms/bloom/v3"
    "github.com/redis/go-redis/v9"
)

var ctx = context.Background()

func createRedisClient() *redis.Client {
    return redis.NewClient(&redis.Options{
        Addr: "localhost:6379",
    })
}

func main() {
    redisClient := createRedisClient()

    // creating in-memory Bloom filter (expected 10000 items, 0.1% false positive rate)
    bloomFilter := bloom.NewWithEstimates(10000, 0.001)

    cachedURLs := map[string]string{
        "https://example.com/index.html": "<html>Home</html>",
        "https://example.com/about.html": "<html>About</html>",
    }

    // adding cached urls to redis and bloom filter
    for url, html := range cachedURLs {
        redisClient.Set(ctx, url, html, 0)
        bloomFilter.AddString(url)
    }

    // test lookups
    testURLs := []string{
        "https://example.com/index.html",
        "https://example.com/contact.html",
        "https://example.com/about.html",
        "https://example.com/fake.html",
    }

    for _, url := range testURLs {
        fmt.Printf("\nChecking %s\n", url)
        if !bloomFilter.TestString(url) {
            fmt.Println("Not in Bloom filter → definitely not cached. Skipping Redis.")
            // fetch from root source
            continue
        }

        fmt.Println("Possibly cached → checking Redis...")
        val, err := redisClient.Get(ctx, url).Result()

        if err == redis.Nil {
            fmt.Println("Redis miss (false positive)")
            // fetch from root source
        } else if err != nil {
            log.Printf("Redis error: %v\n", err)
        } else {
            fmt.Printf("Redis hit: %s\n", val)
        }
    }
}

Malware Scanner

We’ll build a malware scanner that checks if a file is a malware. We’ll create bloom filter using known malware hashes and validate file hashes through it.

package main

import (
    "crypto/sha256"
    "encoding/hex"
    "fmt"
    "io"
    "log"
    "os"
    "path/filepath"

    "github.com/bits-and-blooms/bloom/v3"
)

// simulated known malware hashes (usually you'd get these from a database or API)
var knownMalwareHashes = []string{
    "b1946ac92492d2347c6235b4d2611184", // dummy hash
    "5d41402abc4b2a76b9719d911017c592",
    "9e107d9d372bb6826bd81d3542a419d6",
}

func sha256sum(filePath string) (string, error) {
    f, err := os.Open(filePath)
    if err != nil {
        return "", err
    }
    defer f.Close()

    h := sha256.New()
    if _, err := io.Copy(h, f); err != nil {
        return "", err
    }
    return hex.EncodeToString(h.Sum(nil)), nil
}

func main() {
    // creating a Bloom filter: expect 10000 items, false positive rate 0.1%
    filter := bloom.NewWithEstimates(10000, 0.001)

    // add known malware hashes to the Bloom filter
    for _, hash := range knownMalwareHashes {
        filter.AddString(hash)
    }

    // directory to scan
    targetDir := "~/Downloads"

    // walk through files
    err := filepath.Walk(targetDir, func(path string, info os.FileInfo, err error) error {
        if err != nil {
            log.Println("Error accessing file:", err)
            return nil
        }

        if info.IsDir() {
            return nil
        }

        // compute SHA256 hash
        hash, err := sha256sum(path)
        if err != nil {
            log.Println("Failed to hash:", path, err)
            return nil
        }

        // check Bloom filter
        if filter.TestString(hash) {
            fmt.Printf("WARNING: File %s might be malware (hash match: %s)\n", path, hash)
        } else {
            fmt.Printf("Safe: %s\n", path)
        }

        return nil
    })

    if err != nil {
        log.Fatal(err)
    }
}

These examples can be extended to many other use cases. Some more practical applications are username checks, deduplication systems, CDN lookups, social media already seen items, email spam detection and many more.