jonnify·redis /redis-patterns/data-modeling/bitmap-patterns/1-bit-flags

R7.04.1 · dive 1

A flag is a bit, not a key.

A boolean per entity does not need a key per entity. A Redis bitmap is one String where bit number N is the flag for entity N — set it with SETBIT, read it with GETBIT.

The whole encoding rests on one identity: the bit offset is the entity's integer id. There is no map from id to slot to maintain — the id is the slot. That makes a flag O(1) to set and read, and a million flags fit in 125 KB. Select a command; the readout runs it on the grid below.

A 32-bit window of users:active. Select a command; the grid lights the bits it touches and the readout reports its result.
offsets 0–31 · set: 1, 4, 9, 18, 23, 29
readout · Select a command. The grid is a 32-bit window of users:active with bits 1, 4, 9, 18, 23, 29 set — six active users.
§1

SETBIT and GETBIT — the offset is the id

bitmap-patterns.md.txt · address a bit by offset

SETBIT key offset value sets the single bit at offset to 0 or 1 and returns its previous value; the underlying String grows automatically so the byte holding offset exists, zero-filled. GETBIT key offset returns the bit (0 for any offset past the end). Both are O(1):

SETBIT users:active 1000 1     # entity 1000 is active
GETBIT users:active 1000       # → 1
SETBIT users:active 1000 0     # clear it
GETBIT users:active 1000       # → 0

Because the offset is the id, setting "user 1000 is active" writes bit 1000 — byte 125, bit 0 within it — and nothing else. No per-entity key, no key name to store a million times, no expiration metadata per flag. The flag for the next entity is the next bit. A bitmap is one boolean across many entities (online:users, a bit per user) or many booleans for one entity (user:5000:features, a bit per feature) — same two commands, read either way.

A boolean per entity is one bit at the offset that is the id. There is nothing to look up and nothing per-entity to allocate; SETBIT and GETBIT are the whole interface.

§2

Feature flags — bits are features

bitmap-patterns.md.txt · one entity's many booleans

When the bitmap belongs to one entity, the offsets are its features:

# bit 0 = dark_mode, bit 1 = notifications, bit 2 = beta_features, ...
SETBIT user:5000:features 0 1      # enable dark mode
SETBIT user:5000:features 2 1      # enable beta features
GETBIT user:5000:features 1        # notifications enabled?  → 0
GET    user:5000:features          # the raw byte(s) — decode all flags client-side

Each user's flag set is a handful of bits in one key. A single-feature check is GETBIT; reading every flag at once is a GET of the String and a client-side decode, which is one round trip instead of one per flag. For a few dozen features per user, the whole record is a byte or two.

The trade against a Hash of named booleans is legibility for density: a Hash field notifications reads itself, a bit at offset 1 needs a legend. Use a bitmap when the flag set is large and dense and the memory matters; use a Hash when there are a few flags and a reader needs the names.

A bitmap reads two ways — one bit per entity, or one bit per feature for one entity. Choose it over a named Hash when density wins over legibility.

§3

The density — why a bit beats a key

bitmap-patterns.md.txt · the memory table

The reason to encode a flag as a bit rather than a key is the memory table from the source:

EntitiesBitmap sizeEquivalent Set of ids
1 million125 KB~50 MB
10 million1.25 MB~500 MB
100 million12.5 MB~5 GB

A bitmap is roughly 400× smaller than a Set of the same ids. A Set stores each id as a member with its own overhead; a bitmap stores each as one bit. For "is this entity flagged" at scale — active today, online now, opted into a feature — the bitmap is the dense answer. Select a store to read the gap:

One million flags, three stores. Select one; the readout reports its size and the ratio to a bitmap.
bitmap ~125 KB Set of ids ~50 MB key per entity largest
readout · Select a store. One million flags is ~125 KB as a bitmap, ~50 MB as a Set of ids — about 400× — and most as a key per entity.

The caveat is the highest-offset rule, which the daily-active patterns dive returns to: a bitmap allocates up to its largest set offset, so the density holds only over a dense id space. A sparse id space (hashed or random ids) wastes the bytes between the set bits.

the pattern

One bit per entity, SETBIT/GETBIT at O(1), 125 KB per million flags — a flag is a bit, not a key.

where it grounds

A standalone core-Valkey example. Bitmaps are not an EchoMQ surface — EchoMQ ships no SETBIT/BITCOUNT. The branded placement (placement(id) → 234878118) is the integer offset a branded-native bitmap would use.

The cheapest boolean-per-entity store is a bit, not a key: SETBIT/GETBIT put one flag in one bit at the offset that is the id, and a million flags fit in 125 KB. It holds while the id space is dense — the limitation the daily-active dive makes concrete.

Notes on ValkeySETBIT key offset value and GETBIT key offset operate on the String type; the bit is addressed from the most-significant bit of byte 0, and SETBIT grows the String (zero-filled) to reach the offset, which must be a non-negative integer under 2³² — valkey.io/commands/setbit.

R7.04.2 · in this course

bitcount aggregates

Once flags are bits, the analytics are bit arithmetic: BITCOUNT for how many are set, BITOP to combine bitmaps into cohorts. The next dive: bitcount aggregates.

R7.02 · in this course

The memory family

The bitmap is the densest of the memory techniques. The compact encodings and bounded structures that keep the rest of a keyspace small are the memory optimization module.

§4

References

sources · related

Sources

Related in this course