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;
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode
Redis can calculate the result with a single command:
BITOP AND result vip push_optin active30 follow_voucher_hot
Enter fullscreen mode Exit fullscreen mode
Need the number of matched users?
BITCOUNT result
Enter fullscreen mode Exit fullscreen mode
- 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
Enter fullscreen mode Exit fullscreen mode
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 DescriptionSETBIT
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
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode
You would execute:
BITOP OR tmp bitmap_1 bitmap_2 EXPIRE tmp 60 BITOP AND result tmp bitmap_3 DEL tmp
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode
Using Redis:
BITOP NOT tmp mute
BITOP AND result active30 tmp
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode
Redis implementation:
BITOP XOR tmp active30 mute
BITOP AND result tmp active30
Enter fullscreen mode Exit fullscreen mode
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.
답글 남기기