Redis Bitmaps: Track Millions of Active Users with Just 122KB of RAM
A practical system-design guide to tracking user online/offline status using Redis Bitmaps. Calculate RAM requirements, learn core commands, and solve real-world production pitfalls.
One of the most common requirements in modern social apps, chat services, and collaborative dashboards is tracking user online/offline status. At first glance, it feels like a simple Boolean flag. However, when your system scales to millions of active users, a naive implementation can quickly bring your database to its knees.
This guide explores a textbook system-design solution: Redis Bitmaps. By representing user status as individual bits, you can track millions of users in a fraction of a megabyte with O(1) read and write speeds.
The Problem: The High Cost of the is_online Column
In a traditional relational database, you might add an is_online column to the users table or store a timestamp like last_active_at. Every time a user loads a page, clicks a button, or sends a WebSocket handshake, your application executes a query:
UPDATE users SET is_online = 1, last_active_at = NOW() WHERE id = 12345;This works fine for a few thousand users. But at scale, this approach runs into major bottlenecks:
- Write Amplification: Databases are optimized for structured reads, but writing is relatively slow and involves updating indices, writing to transaction logs (WAL), and hitting disk storage.
- Connection Pool Exhaustion: Millions of active users sending heartbeats every few seconds will quickly saturate your database connection pool.
- Lock Contention: Frequent updates to the same user rows lead to locking overhead, degrading the performance of other critical features.
The Solution: What is a Redis Bitmap?
Instead of treating online status as an entire row or table entry, we can treat it as a single binary digit (bit):
1represents Online0represents Offline
Redis provides a specialized set of commands to treat string values as arrays of bits. This feature is called Bitmaps. In a Redis Bitmap, the offset in the bit array corresponds to the user's ID, and the bit value (0 or 1) represents their status.
Because Redis operates entirely in-memory, bitwise operations are incredibly fast—taking O(1) constant time for setting and retrieving status.
The Math: Calculating Memory Footprint
To see why Bitmaps are so powerful, let's break down the memory math. In a Bitmap:
Memory usage = 1 bit per user
Let's calculate the RAM needed to store the online status of 1,000,000 users:
- Total Bits: 1,000,000 bits
- Convert to Bytes: 1,000,000 bits / 8 = 125,000 bytes
- Convert to Kilobytes: 125,000 bytes / 1024 ≈ 122 KB
Here is how memory usage scales at different user thresholds:
| User Count | Total Bits | Memory Size (KB/MB) | | :--- | :--- | :--- | | 1,000,000 | 1,000,000 bits | ~122 KB | | 5,000,000 | 5,000,000 bits | ~610 KB | | 10,000,000 | 10,000,000 bits | ~1.2 MB | | 50,000,000 | 50,000,000 bits | ~6.0 MB |
Even if you have 50 million registered users, your entire active-status index fits into 6 megabytes of RAM. That is a trivial cost for any modern server.
Implementation: Core Redis Commands
Trialling Bitmaps in Redis requires only four simple commands: SETBIT, GETBIT, and BITCOUNT. Let's assume we use the key user:online:bitmap.
1. Marking a User Online
When user 12345 connects (e.g., establishes a WebSocket connection), set the bit at offset 12345 to 1:
SETBIT user:online:bitmap 12345 12. Marking a User Offline
When user 12345 disconnects or logs out, set the bit back to 0:
SETBIT user:online:bitmap 12345 03. Checking User Status
To determine if user 12345 is currently online:
GETBIT user:online:bitmap 12345
# Returns 1 (Online) or 0 (Offline)4. Counting All Online Users
To get the total number of users currently online (the count of all 1 bits):
BITCOUNT user:online:bitmap
# Returns the total active user countProduction Pitfalls & Solutions
While Bitmaps are highly efficient, bringing them into a real production system requires navigating a few critical gotchas.
Pitfall 1: String-based IDs (UUIDs) and Sparse IDs
The bit offset must be an integer. If your system uses UUIDs (like e5b02bcf-a8e5-4d7a...), you cannot use them directly as offsets.
Additionally, Redis allocates memory dynamically based on the highest offset. If you have only two users with IDs 1 and 99,999,999, Redis will allocate memory for all 100 million bits in between—consuming around 12MB of RAM just to store two statuses.
Solutions:
- Auto-Increment IDs: Ensure you only use sequential integer IDs.
- ID Mapping: If you must use UUIDs, keep a Redis Hash (or an internal service) that maps UUIDs to a sequential integer series starting from
0.
Pitfall 2: Silent Disconnects (The Heartbeat Problem)
What happens if a user's phone dies, they lose internet connectivity, or their app crashes? The application server will not be able to execute SETBIT user:online:bitmap <user_id> 0. That user will appear online indefinitely.
Furthermore, individual bits in a Redis Bitmap do not support Time-to-Live (TTL). You cannot set an expiration time on a single bit.
Solutions:
- WebSocket Heartbeats: Require clients to ping the server every 1-5 minutes. Every time the server receives a heartbeat, it runs
SETBIT <id> 1. - Key Rotation: To clean up users who disappeared without sending an offline signal, clear or rotate your bitmaps periodically.
Scaling Up: Time-Bucketed Bitmaps
To solve the heartbeat cleanup issue and gain deeper analytics, a common pattern is Time-Bucketed Bitmaps (e.g., daily or hourly keys).
Instead of one global key, store activity in keys containing dates or hours:
user:online:2026-06-03user:online:2026-06-03:10h
Every time a user pings, set their bit in the current hourly/daily bitmap:
SETBIT user:online:2026-06-03:10h 12345 1
EXPIRE user:online:2026-06-03:10h 86400 # Set key TTL to 24 hoursBy adding a TTL to the whole key, inactive user data is automatically cleaned up by Redis when the key expires, avoiding memory leaks.
Real-World Benefit: Computing DAU and MAU
Storing daily active users in separate bitmaps allows you to compute analytical queries instantly using bitwise operations. For example, to calculate the Daily Active Users (DAU) across three days, you can run BITOP OR to merge the keys and get the total count:
# Perform bitwise OR on 3 days of bitmaps and save the result to a temporary key
BITOP OR dau:three_days user:online:2026-06-01 user:online:2026-06-02 user:online:2026-06-03
# Count the unique users who logged in during those three days
BITCOUNT dau:three_daysThis operation runs in-memory on the Redis server and takes only milliseconds, saving you from complex, slow SQL queries on raw event logs.
Summary & Recommendations
Redis Bitmaps are a fantastic choice when:
- You need high-speed, low-overhead binary status tracking (e.g., online/offline, feature flags, newsletter subscriptions).
- Your user IDs are dense, sequential integers.
- You want to perform quick analytics like DAU/MAU.
However, avoid Bitmaps if:
- You need to track complex states (e.g., "Away", "Busy", "Do Not Disturb").
- You must store metadata, such as the user's IP address, device type, or the exact millisecond they went offline.
- Your ID space is sparse or relies entirely on UUIDs without an intermediate mapping layer.
If you are currently scaling your backend and database, building a resilient base is key. Learn more about structure in NestJS Modular Architecture: Building Production APIs That Scale, or ensure your public-facing apps are discovery-ready using Next.js SEO Checklist for the App Router.
Facing performance issues or scaling challenges?
I specialize in building low-latency map infrastructure, real-time streaming pipelines (Kafka, ClickHouse), and highly optimized backend systems. Let's work together to scale your product.
Related Articles
12 Jun 2026
Migrating from Google Maps API to a Vietnam Map API: Cost & Code
Compare Google Maps Platform pricing and Vietnam limitations, then migrate geocoding, reverse geocoding, and address autocomplete to GoGoDuk with real code.
8 Jun 2026
Redis 8.8 Released: New Native Rate Limiter, Array Data Structure, and Up to +83% Performance Boost
A deep technical breakdown of the newly released Redis 8.8 (June 2, 2026). Explore the new O(1) sparse-friendly Array structure by antirez, the native INCREX rate-limiting command, and Hash field-level subkey notifications.
8 Jun 2026
PostGIS Performance Tuning: From 2s to 10ms for Vietnamese Spatial Queries (Gogoduk Case Study)
Explore practical PostGIS database optimization techniques from the Gogoduk Map API project. Learn how migrating from Geometry to Geography, designing Partial GIST indexes, and simplifying polygons can achieve 10ms query times.
8 Jun 2026
Redis Lua Script & SETNX: High-Performance Rate Limiting & Quota Alerting for APIs
Learn how Gogoduk builds API Rate Limiting and Quota Alerting systems with Redis and Go. Discover how to use Lua scripts for atomicity, prevent memory leaks, and leverage SETNX to deduplicate notifications.