rust 95 lines · 3 tabs

Bloom Filter for Deduplicating Seen URLs in a Web Crawler

Shared by codesnips Sep 2026
3 tabs
use crate::hashing::double_hash;

pub struct BloomFilter {
    bits: Vec<u64>,
    m: usize, // number of bits
    k: u32,   // number of hash probes
}

impl BloomFilter {
    pub fn with_params(n: usize, p: f64) -> Self {
        let ln2 = std::f64::consts::LN_2;
        let m = (-(n as f64) * p.ln() / (ln2 * ln2)).ceil() as usize;
        let k = ((m as f64 / n as f64) * ln2).round().max(1.0) as u32;
        let words = (m + 63) / 64;
        BloomFilter { bits: vec![0u64; words], m, k }
    }

    fn indices(&self, key: &str) -> impl Iterator<Item = usize> + '_ {
        let (h1, h2) = double_hash(key);
        let m = self.m as u64;
        (0..self.k as u64).map(move |i| (h1.wrapping_add(i.wrapping_mul(h2)) % m) as usize)
    }

    pub fn insert(&mut self, key: &str) {
        for idx in self.indices(key) {
            self.bits[idx / 64] |= 1u64 << (idx % 64);
        }
    }

    pub fn contains(&self, key: &str) -> bool {
        self.indices(key).all(|idx| self.bits[idx / 64] & (1u64 << (idx % 64)) != 0)
    }
}
3 files · rust Explain with highlit

A bloom filter is a compact probabilistic set: it answers "have I seen this URL?" using a fraction of the memory a HashSet<String> would need, at the cost of occasional false positives (it may claim to have seen a URL it hasn't) but never false negatives. This makes it ideal for a crawler frontier, where the goal is to avoid re-fetching pages and a rare skipped-but-actually-new URL is an acceptable trade for keeping billions of visited URLs in RAM.

In bloom.rs, the filter is a bit vector (Vec<u64> used as packed bits) plus k independent hash positions per key. Rather than instantiating k real hash functions, the code uses the Kirsch–Mitzenmacher double-hashing trick: two base hashes h1 and h2 are combined as h1.wrapping_add(i * h2) to derive each of the k indices. BloomFilter::with_params sizes the bit array m and derives k from the target item count n and desired false-positive rate p using the standard formulas m = -n·ln(p)/ln(2)^2 and k = (m/n)·ln(2). insert sets bits; contains returns false only if any bit is unset, which is the guarantee that underpins zero false negatives.

hashing.rs isolates the two base hashes so the filter stays agnostic about the algorithm. It uses DefaultHasher seeded with two different constants, giving statistically independent 64-bit values — cheap and dependency-free, though a production system might prefer a faster non-cryptographic hash like xxHash.

frontier.rs shows the real use: UrlFrontier wraps the filter with a queue and a Mutex so worker threads can call add_url concurrently. The critical detail is contains-then-insert under one lock, avoiding a race where two threads both enqueue the same URL. It also normalizes URLs before hashing so trivial variations collapse to one key.

The main pitfall to remember: a bloom filter cannot delete or resize cleanly, and its accuracy degrades as it fills past the design capacity n, so with_params must be sized for the expected crawl volume up front. When false positives become intolerable, engineers reach for a counting or scalable bloom variant instead.


Related snips

Share this code

Here's the card — post it anywhere.

Bloom Filter for Deduplicating Seen URLs in a Web Crawler — share card
Link copied