R7.03.2 · dive 2
Membership, one-sided error.
A Bloom filter answers “is this item in the set?” with possible false positives but zero false negatives — a “no” is certain, a “yes” is “probably”.
That one-sided error is the whole point: it is safe to skip work on a “no”
because a “no” is never wrong. Select a query below — a present item, an absent item, a
false positive — and read what the bits say. It is also the error in the wrong direction for
EchoMQ's idempotency, which keeps an exact de: key.
How a Bloom filter works
probabilistic-data-structures.md.txt · membershipA Bloom filter is a bit array plus k hash functions. To add an item, hash it k ways and set the k bits. To test an item, hash it the same k ways and read those bits: if any bit is zero the item is definitely absent; if all are set the item is probably present. A false positive happens when other items happened to set all of one absent item's bits. A false negative cannot happen — adding an item only sets bits, never clears them, so a present item always reads back all-set.
The false-positive rate is a dial. More bits per item and more hash functions lower it; a 1% rate for a million items is a typical reservation. You pay memory and a little CPU for a smaller error. The commands are module commands — Valkey-Bloom or Redis-Stack, not the core engine:
BF.RESERVE usernames 0.01 1000000 # 1% false-positive rate, capacity 1M — a module command
BF.ADD usernames "john_doe"
BF.EXISTS usernames "john_doe"
Notes on ValkeyThe Bloom-filter commands — BF.RESERVE,
BF.ADD, BF.EXISTS, BF.MADD — are provided by the Valkey-Bloom
module, not the core engine, so a deployment must load the module to use them; BF.RESERVE takes a
target error rate and capacity that fix the bit count and the number of hashes —
valkey.io/commands.
A Bloom filter's error is one-sided by construction: a “no” is certain, a “yes” may be a false positive at a rate you set. That asymmetry is what makes it useful in front of an expensive lookup.
The signature use — cache penetration
probabilistic-data-structures.md.txt · cache penetrationWithout a membership pre-check, a flood of requests for keys that do not exist each misses the cache and falls through to the database — an attacker can turn “look up a user that isn't there” into database load. A Bloom filter holding the set of real keys rejects the absent ones immediately: if the filter says “absent”, skip the cache and the database entirely.
The zero-false-negative guarantee is what makes this safe — the filter never wrongly says “absent” for a key that exists, so a real key is never rejected. A false positive only costs one needless lookup, which the cache absorbs. The asymmetry lands exactly where it helps: the dangerous answer (reject a real key) cannot happen, and the harmless answer (one extra lookup) is the only error.
A Bloom filter in front of the cache turns “every absent key hits the database” into “every absent key is rejected in memory”, safely — because a “no” is never wrong and a false “yes” costs only a lookup.
Cuckoo — Bloom with deletion
probabilistic-data-structures.md.txt · CuckooA Bloom filter cannot delete: clearing a bit might unset a bit another item relies on, breaking the
no-false-negative guarantee. A Cuckoo filter stores small fingerprints in a cuckoo hash
table, so it supports deletion. The commands are module commands, CF.*:
CF.RESERVE sessions 1000000
CF.ADD sessions "session:abc123"
CF.EXISTS sessions "session:abc123"
CF.DEL sessions "session:abc123" # delete — what Bloom cannot do
Choose Bloom when the set only grows and inserts dominate; choose Cuckoo when items must leave the filter — tracking active sessions that expire, for instance, where a logged-out session should drop out of the set. Hover the columns below to compare the trade.
Bloom and Cuckoo answer the same membership question with the same one-sided error; Cuckoo adds deletion and faster lookups at a slightly slower insert. Reach for Cuckoo when items must leave the set.
The EchoMQ contrast — exact membership
echo_mq/metrics.ex · echo_mq/jobs.exEchoMQ also answers a membership question — “have I already seen this idempotency key?” — but it answers it exactly, with a real key, not a filter:
# metrics.ex — every id stored at a real key def get_deduplication_job_id(conn, queue, dedup_id) do key = Keyspace.queue_key(queue, "de:" <> dedup_id) ... end -- jobs.ex — @enqueue, the exact dup-refusal by job key if redis.call('EXISTS', KEYS[1]) == 1 then return 0 end
A Bloom filter's error is one-sided in the wrong direction for idempotency. It never wrongly says “absent” — but it can wrongly say “present”, a false positive. For a cache pre-check a false “present” is harmless (one extra real lookup). For dedup a false “present” means “already seen, drop it” — and the job is lost, silently. So EchoMQ stores every id exactly, pays O(n) memory, and accepts zero false positives.
A Bloom (or Cuckoo) filter: membership at a fraction of the exact memory, no false negatives, a tunable false-positive rate. Safe in front of an expensive lookup.
The emq:{q}:de:<dedupId> key (plus the @enqueue EXISTS
refusal) is exact membership: O(n) memory, zero false positives. A false “already seen”
would drop a job, so the filter's one-sided error is unacceptable here.
A Bloom filter's value is its one-sided error — a “no” is certain, so it is safe to skip work on it; a “yes” may be wrong, so confirm it. Use it where a false positive is cheap (a wasted cache lookup). Where a false positive is catastrophic — dropping a job — use the exact key EchoMQ uses.
The exact-dedup home
The emq:{q}:de:<dedupId> key and the @enqueue EXISTS refusal
are taught in full in R7.1, the
system of record: the job HASH is the record of truth, and its membership check is exact for the same
reason the dedup is.
The architecture
The exact membership check follows from the Branded Component System discipline — the branded id is
the identity the de: key is built from. The Branded Component System is the
architecture EchoMQ is built to.
References
sources · relatedSources
- Redis — Bloom filter — the bit array, the k hashes, and the no-false-negative guarantee.
- Redis — BF.RESERVE — create a Bloom filter with a target false-positive rate; a module command.
- Redis — Cuckoo filter — a fingerprint filter that supports deletion.
- Redis — CF.RESERVE — create a Cuckoo filter; a module command.
Related in this course
- R7.03 · Probabilistic data structuresthe module hub and the exact-vs-probabilistic frame
- R7.03.1 · HyperLogLogcardinality, the count question filters do not answer
- R7.03.3 · Count-Min & T-Digestfrequency and percentile estimation
- R7.01.1 · System of recordthe exact de: dedup key in context
- /bcs · The Branded Component Systemthe architecture EchoMQ is built to