Probabilistic cardinality estimation with PFADD/PFCOUNT at constant 12KB, per-user boolean tracking at scale with SETBIT/BITCOUNT, and location-aware radius queries with GEOADD/GEOSEARCH.
F-6 — HyperLogLog, Bitmaps, and Geospatial
Who this module is for: You know Redis has data types beyond the standard five, but you have never understood when to reach for them or what problems they actually solve. This module covers three specialized types that each solve a specific class of problem with dramatically less memory than naive approaches. HyperLogLog for approximate unique counting, Bitmaps for per-user boolean tracking at scale, and Geospatial for location-aware queries.
HyperLogLog
The Problem: Counting Unique Items at Scale
Counting unique visitors to a page sounds simple. Store a Set, add each user ID, read the cardinality with SCARD. Done.
Until your page gets 10 million unique visitors per day. A Redis Set with 10 million string members consumes roughly 500MB of RAM — and you need a separate Set per page, per day.
If you only need to know approximately how many unique visitors there were (and you do not need to know which visitors), HyperLogLog solves this in under 12KB of RAM regardless of cardinality.
What HyperLogLog Is
HyperLogLog (HLL) is a probabilistic algorithm for cardinality estimation. It does not store the actual elements — it stores a compact sketch of the data that can estimate the number of distinct elements with a standard error of 0.81%.
That means: if the true unique count is 1,000,000, HyperLogLog will return a value between ~993,000 and ~1,007,000. For analytics dashboards, A/B test reporting, and "approximately X unique users" displays, this error margin is completely acceptable.
Memory: Each HLL structure uses at most 12KB regardless of whether it has seen 100 or 100 billion distinct values.
Commands
PFADD key element [element ...] → add elements to the HLL, return 1 if internal state changed
PFCOUNT key [key ...] → return estimated cardinality (of union if multiple keys)
PFMERGE destkey sourcekey [...] → merge multiple HLLs into destkey
127.0.0.1:6379> PFADD visitors:2024-01-15 "user:1001" "user:1002" "user:1003"
(integer) 1
127.0.0.1:6379> PFADD visitors:2024-01-15 "user:1001" ← duplicate
(integer) 0 ← no state change
127.0.0.1:6379> PFCOUNT visitors:2024-01-15
(integer) 3
# Unique visitors across two days (union)
127.0.0.1:6379> PFADD visitors:2024-01-16 "user:1002" "user:1004"
(integer) 1
127.0.0.1:6379> PFCOUNT visitors:2024-01-15 visitors:2024-01-16
(integer) 4 ← user:1002 counted once
PFMERGE combines HLLs for multi-period rollups:
PFMERGE visitors:2024-01-week1 visitors:2024-01-11 visitors:2024-01-12 visitors:2024-01-13 visitors:2024-01-14 visitors:2024-01-15
PFCOUNT visitors:2024-01-week1 → unique visitors across the week
When to Use HyperLogLog
- Unique page views per day/hour
- Unique search queries
- Unique API callers per endpoint
- Unique items processed by a pipeline (dedup tracking at scale)
- Any "count distinct" metric where ~1% error is acceptable
Do not use HyperLogLog when:
- You need exact counts (use a Set)
- You need to know which specific items were seen (use a Set)
- Cardinality is small (< 1,000 items) — just use a Set; the memory difference is negligible
Bitmaps
The Problem: Tracking Boolean States per User at Scale
Suppose you want to track daily login streaks. For each user, you need to know: did they log in on day 1? Day 2? Day 365?
Naive approach: one key per user per day. SET login:user:1001:2024-01-15 1. That is 365 keys per user per year. At 1 million users, that is 365 million keys.
Bitmap approach: one key per user per year. Each bit position represents a day. Bit 0 = Jan 1, bit 1 = Jan 2, ..., bit 364 = Dec 31.
At 1 million users and 365 days: 1,000,000 × 365 bits = ~45MB total. The naive approach would consume gigabytes.
What Bitmaps Are
Redis does not have a distinct "Bitmap" type. Bitmaps are a set of bit-manipulation operations on Redis Strings. A String in Redis is a byte array, and Redis lets you address individual bits within that array by offset.
A String of 1 byte can hold 8 bits. A String of 512MB can hold ~4 billion bits.
Commands
SETBIT key offset value → set bit at offset to 0 or 1, return old value
GETBIT key offset → return the bit at offset (0 if key or bit does not exist)
BITCOUNT key [start end] → count set bits (optionally in byte range)
BITPOS key bit [start end] → find first bit set to 0 or 1
BITOP AND|OR|XOR|NOT destkey key [key ...] → bitwise operation on multiple keys
Tracking Daily Logins
# User 1001 logs in on day 14 of the year (0-indexed)
SETBIT logins:user:1001:2024 14 1
(integer) 0 ← previous value was 0
# User 1001 logs in on day 15
SETBIT logins:user:1001:2024 15 1
# How many days did user 1001 log in this year?
BITCOUNT logins:user:1001:2024
(integer) 2
# Did user 1001 log in on day 14?
GETBIT logins:user:1001:2024 14
(integer) 1
# First day user 1001 logged in
BITPOS logins:user:1001:2024 1
(integer) 14
Sign in to keep reading
The rest of this module is free — sign in with Google to unlock it and track your progress.
Sign in & RegisterSee it in production
Discussion
0Join the discussion