ConceptDifficulty: Intermediate2 min

Bloom Filters: Proving a Key Is Absent

"Definitely not here" or "maybe here", and never the other way round.

A Bloom filter is a compact structure that answers whether a key might be in a set. Its answer "absent" is certain; its answer "present" means only "probably". That one-sided certainty is enough to stop requests for keys that exist nowhere from reaching the database at all.

The problem it solves here. A cache helps only with keys it has seen. Requests for keys that exist nowhere, such as user ids an attacker makes up, miss the cache every time and reach the database every time. This is called cache penetration. A Bloom filter holding every key that exists can answer "absent" before the cache or the database is touched.

How it works. The filter is an array of bits, all zero at the start. To add a key, several hash functions each turn it into a position, and those bits are set to one. To look a key up, the same positions are checked: if any is zero, the key was never added. If all are one, it may have been added, or other keys may have set those bits.

An eight-bit Bloom filter with two positions per key, run with python:3.12-slim (Python 3.12.13)

InputPositions from the first two bytes of each key's SHA-256 hash, each taken modulo 8

  1. start

    1. 0
    2. 0
    3. 0
    4. 0
    5. 0
    6. 0
    7. 0
    8. 0

    All bits clear

  2. insert user:2

    1. 0
    2. 1set
    3. 0
    4. 0
    5. 0
    6. 1set too
    7. 0
    8. 0

    Positions 1 and 5 set

  3. insert user:3

    1. 0
    2. 1
    3. 0
    4. 1set
    5. 1set too
    6. 1
    7. 0
    8. 0

    Positions 3 and 4 set

  4. insert user:4

    1. 1set
    2. 1
    3. 0
    4. 1
    5. 1
    6. 1
    7. 1set too
    8. 0

    Positions 0 and 6 set

  5. lookup user:101

    1. 1
    2. 1
    3. 0checked, 0
    4. 1
    5. 1
    6. 1
    7. 1
    8. 0also 0

    A zero bit: definitely absent

  6. lookup user:100

    1. 1
    2. 1
    3. 0
    4. 1checked, 1
    5. 1
    6. 1
    7. 1also 1
    8. 0

    Both bits set: maybe present, but user:100 was never added

Resultuser:101 is proved absent. user:100 is a false positive: its bits were set by user:3 and user:4.

What a false positive costs. A "maybe" sends the request on to the cache and the database, which answer correctly; the filter has saved nothing on that request, but done no harm. A larger array and more hash functions make false positives rarer. The filter can never wrongly say "absent", so it never hides a key that exists.

Where else you meet it. Cassandra keeps a Bloom filter for each data file on disk, so a read can skip every file that cannot hold the key.

So trust a Bloom filter's "absent" and check its "present": the certainty runs in one direction only.