User segmentation requirement
Imagine you need to send a push notification to users who satisfy all of the following conditions:
- Push notification is enabled
- User is a VIP
- Active within the last 30 days
- Following the Voucher Hot category
The traditional approach is to query multiple tables:
SELECT DISTINCT u.id
FROM users u
JOIN user_configs c ON c.user_id = u.id
JOIN devices d ON d.user_id = u.id
JOIN follows f ON f.user_id = u.id
WHERE c.push_optin = 1
AND c.mute = 0
AND d.fcm_token IS NOT NULL
AND f.category = 'voucher_hot'
AND u.last_active >= NOW() - INTERVAL 30 DAY;
As your user base grows into the millions, every campaign requires joining multiple large tables, filtering millions of records, and repeatedly computing the same audience. Query latency increases significantly, making real-time segmentation increasingly difficult.
A Different Approach
Instead of querying the database every time, we precompute each boolean attribute as a bitmap.
Think of a bitmap as a huge array containing only 0 and 1, where the index corresponds to the user ID.
For example, a bitmap representing whether a user has enabled push notifications:
- User ID :
0 1 2 3 4 5 6 7 - Bitmap :
1 0 1 1 0 0 1 1
To check whether user 123 has enabled notifications, simply read bit 123.
1 → enabled
0 → disabled
Each bitmap represents exactly one boolean property:
- bitmap:push_optin
- bitmap:vip
- bitmap:active30
- bitmap:follow:voucher_hot
Memory Usage
Bitmap is extremely memory efficient.
Each user requires only one bit.
For 1 million users:
1.000.000 bits
~ 125.000 bytes
~ 122 KB
That means every segment only consumes about 122 KB of Redis memory.
Even 100 different segments require only around 12 MB.
Finding Intersections
Suppose you want all users that are:
VIP
AND Push Opt-in
AND Active30
AND Following Voucher Hot
Redis can calculate the result with a single command:
BITOP AND result vip push_optin active30 follow_voucher_hot
Need the number of matched users?
BITCOUNT result
- No SQL joins.
- No table scans.
- No iterating over millions of users in application code.
Why Is It So Fast?
Redis Bitmap is implemented on top of the Redis String data type. Internally, Redis stores the bitmap as a contiguous sequence of bytes, allowing the CPU to process memory sequentially without pointer chasing or hash lookups.
When executing BITOP, Redis doesn't process users one by one. Instead, the CPU performs bitwise operations directly on blocks of memory.
For example:
10110010
11001000
--------
10000000
On modern 64-bit CPUs, each instruction processes at least 64 bits at once, and many processors can process even larger blocks using SIMD instructions.
This is why bitmap operations over millions of users typically complete in just a few milliseconds.
Bitmap Commands
The Redis commands you'll use most frequently are:
Command Purpose
| Command | Syntax | Description |
|---|---|---|
SETBIT |
SETBIT key offset value |
Set the bit at offset to 0 or 1. |
GETBIT |
GETBIT key offset |
Get the bit value at offset. |
BITCOUNT |
BITCOUNT key [start end [BYTE or BIT]] |
Count the number of bits set to 1. |
BITPOS |
BITPOS key bit [start [end [BYTE or BIT]]] |
Find the position of the first 0 or 1 bit. |
BITOP |
BITOP <AND, OR, XOR, NOT> destkey key [key ...] |
Perform bitwise operations across one or more bitmaps and store the result in destkey. |
Chaining Multiple Operations
BITOP supports applying a single operation to multiple bitmaps in one command.
For example, to compute the intersection of three segments:
BITOP AND result bitmap_1 bitmap_2 bitmap_3
However, BITOP only supports one operation per command. For nested expressions, you'll need to create temporary bitmaps.
For example, to evaluate:
(bitmap_1 OR bitmap_2) AND bitmap_3
You would execute:
BITOP OR tmp bitmap_1 bitmap_2 EXPIRE tmp 60 BITOP AND result tmp bitmap_3 DEL tmp
Since intermediate bitmaps are only temporary, either delete them immediately after use or assign a short TTL to ensure they are cleaned up automatically, especially if the process fails before reaching the cleanup step.
A Common Pitfall: EXCLUDE
Many developers implement exclusion like this:
A AND NOT B
Using Redis:
BITOP NOT tmp mute
BITOP AND result active30 tmp
This can introduce a silent bug.
BITOP NOT only inverts the existing length of the source bitmap. If mute is shorter than active30, Redis pads the remaining bits with zeros during the AND operation, unintentionally excluding many valid users.
The safer approach is:
(A XOR B) AND A
Redis implementation:
BITOP XOR tmp active30 mute
BITOP AND result tmp active30
This produces the correct result even when the two bitmaps have different lengths.
Conclusion
Redis Bitmap doesn't replace your database.
Instead, it acts as an in-memory index for boolean attributes. Once each attribute is represented as a bitmap, complex audience expressions become a series of CPU-optimized bitwise operations instead of expensive SQL joins.
For systems handling millions of users, Redis Bitmap is one of the simplest and most effective techniques for building high-performance segmentation engines.
Top comments (0)