Module F-6·20 min read

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-5 — 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

text
text

PFMERGE combines HLLs for multi-period rollups:

text

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

text

Tracking Daily Logins

text

Population-Wide Daily Active Users

Instead of per-user keys, use per-day keys where the offset is the user ID:

text

Memory note: A bitmap for 1 million users requires 1,000,000 bits = 125KB. Even with 100 million users, a single DAU bitmap is 12.5MB — manageable for per-day tracking.

Caveat: Bit offsets are allocated based on the maximum offset ever set. If user ID 99,999,999 logs in, Redis allocates a ~12MB string immediately, even if only that one user exists. Numeric IDs must be dense (not UUIDs) for Bitmaps to be memory-efficient.

Feature Flags and A/B Testing

text

At millions of users, this is significantly more memory-efficient than a Set of user IDs with the feature enabled.

When to Use Bitmaps

  • Daily/weekly active user tracking (compact per-day bitfield)
  • Login streaks and retention cohorts
  • Feature flag rollouts (user IDs as offsets)
  • Read receipts and notification tracking at scale
  • Any boolean-per-user-per-day pattern

Do not use Bitmaps when:

  • User IDs are not dense integers (UUIDs → wasteful; use a Set or Bloom filter)
  • The per-item state has more than 2 values (use a Hash or sorted set per user)
  • You need to iterate over the set of users with a flag enabled (use a Set; BITPOS finds the first but iterating all is awkward)

Geospatial

The Problem: Location-Based Queries

Storing and querying user locations — "find all drivers within 5km of this point" — requires either a spatial index or clever approximation. Redis's Geo commands give you a spatial index backed by a Sorted Set.

What Geospatial Is

Redis Geo is not a separate type — it is a set of commands that operate on a Sorted Set where the score is a 52-bit Geohash encoding of the longitude/latitude. This means every GEO* command is ultimately stored and retrieved via the Sorted Set's skip list, and you can use ZRANGE on a Geo key if needed.

The encoding achieves roughly ±0.0001° precision (about ±0.6 metres at the equator — sufficient for any real-world use case).

Commands

text

The older GEORADIUS and GEORADIUSBYMEMBER commands are deprecated in Redis 6.2 in favour of GEOSEARCH. Use GEOSEARCH.

Building a Nearby Drivers Feature

text

Updating a Driver's Location

Since Geo uses a Sorted Set internally, updating a member's position is the same as re-adding it:

text

GEOADD with an existing member updates the score (position). No need to delete first.

Removing a Member

ZREM drivers:online "driver:101" ← removes the member from the underlying Sorted Set

Or use GEOADD ... XX to only update existing members (won't add if missing) or NX to only add new ones (won't update if existing).

BYBOX is useful for map viewport queries:

text

Precision and Limitations

The Geohash encoding has a precision of ~0.6m. For most location-aware applications (ride-hailing, delivery, nearby businesses), this is more than sufficient. For sub-metre precision (surveying, GPS coordinates with cm accuracy), use a dedicated spatial database.

Geo keys are plain Sorted Sets. This means:

  • ZCARD drivers:online tells you how many drivers are in the index
  • ZSCAN iterates over all drivers
  • There is no built-in TTL per member — to remove a driver who goes offline, call ZREM
  • For dynamic driver locations (updating every few seconds), consider whether the index size stays manageable

When to Use Geospatial

  • Ride-hailing / delivery — nearby driver search
  • Restaurant/store finder — "near me" queries
  • Delivery radius validation — is this address within our delivery zone?
  • Location-aware notifications — alert users near an event
  • Any "find X within Y distance of Z" query

Do not use Redis Geo when:

  • You need complex spatial queries (polygon containment, routing, map matching) — use PostGIS
  • Precision below 1 metre is required
  • You need to store and query trajectory data over time (use a time-series + PostGIS)

Comparing the Three Types

TypeProblem SolvedMemoryAccuracyUse Case
HyperLogLogCount distinct elements≤ 12KB always~99.2% (0.81% error)DAU, unique visitors, distinct queries
BitmapBoolean per-user tracking1 bit per userExactLogin streaks, DAU, feature flags
GeospatialLocation + radius queries~88 bytes per member±0.6mNearby search, delivery, ride-hailing

The common thread: each type trades something (exactness, raw flexibility, or simplicity) for a massive reduction in memory or query complexity at scale. Knowing when to make that trade is the mark of a Redis practitioner.


Summary

  • HyperLogLogPFADD / PFCOUNT / PFMERGE. Estimates cardinality with ±0.81% error in ≤ 12KB. Use for unique visitor counts, distinct query tracking.
  • BitmapsSETBIT / GETBIT / BITCOUNT / BITOP. Boolean state per dense integer ID, 1 bit per user. Use for login streaks, DAU, feature flags.
  • GeospatialGEOADD / GEOSEARCH / GEODIST. Location index backed by a Sorted Set, ~0.6m precision. Use for nearby search, delivery radius, ride-hailing.
  • All three solve problems that would require dramatically more memory with naive Set or String approaches.

Next: F-6 — Pipelining and the RESP Protocol — how to batch commands to eliminate network round-trips, and why naive await redis.get() in a loop is one of the most common Redis performance mistakes.


Knowledge Check

A media site needs to display the number of unique daily readers for a trending article. At peak, the article might receive millions of distinct visitors. The exact number is not critical; an estimate within 1% is perfectly acceptable. Which Redis data structure is designed to solve this with minimal memory footprint, and roughly how much memory will it consume?


An engineering team wants to track whether millions of users logged into their app on a specific day using Bitmaps (SETBIT active_users:2025-01-01 <user_id> 1). They notice that for some markets, this approach uses almost no memory, but for a newly acquired subsidiary, the memory usage spikes to over 100MB per day even though only a few thousand users logged in. What is the most likely cause?


A food delivery app uses Redis to match customers with nearby couriers. The architecture relies on GEOADD couriers:online <lon> <lat> <courier_id> and GEOSEARCH to find matches within a 5km radius. When a courier goes offline, what is the correct way to remove them from the spatial index?

Test your knowledge with more question sets

Sign in to access a wider variety of questions and get notified when new practice sets are added to this module.

Sign in & Register

Discussion

0

Join the discussion

Loading comments...

© 2026 Jatin Jain Saraf (JJS). All rights reserved.