Geo + specialized data.

Geo

GEOADD pois 13.4 52.5 "Brandenburg Gate"
GEOADD pois 13.5 52.6 "Tegel" 13.3 52.4 "Schoenefeld"

GEOPOS pois "Brandenburg Gate"
GEODIST pois a b km
GEOHASH pois "Brandenburg Gate"

Radius

GEOSEARCH pois FROMMEMBER "Brandenburg Gate" BYRADIUS 5 km
GEOSEARCH pois FROMLONLAT 13.4 52.5 BYRADIUS 10 km COUNT 20 ASC
GEOSEARCH pois FROMLONLAT 13.4 52.5 BYBOX 10 10 km ASC

Returns sorted by distance.

Driver delivery example

def add_driver(driver_id, lat, lng):
    redis.geoadd("drivers", lng, lat, driver_id)

def nearby_drivers(lat, lng, radius_km=5, limit=10):
    return redis.geosearch(
        "drivers",
        longitude=lng, latitude=lat,
        radius=radius_km, unit="km",
        count=limit, sort="ASC",
    )

def remove_driver(driver_id):
    redis.zrem("drivers", driver_id)

TTL on geo entries

Geo is built on zset; no native per-member TTL. Workarounds:

  • Periodic cleanup (every N min).
  • Separate keys per time-bucket: drivers:{floor(time/60)}.

HyperLogLog (count uniques)

PFADD uniques:2026-01-15 user1 user2 user3
PFCOUNT uniques:2026-01-15
PFMERGE uniques:week uniques:mon uniques:tue uniques:wed

12KB max per HLL; ~0.81% error. Counts billions with tiny memory.

Daily / weekly / monthly active users

def track(user_id):
    today = date.today().isoformat()
    redis.pfadd(f"dau:{today}", user_id)

def dau(date):
    return redis.pfcount(f"dau:{date}")

def wau(date):
    days = [f"dau:{(date - timedelta(days=i)).isoformat()}" for i in range(7)]
    return redis.pfcount(*days)

Bitmap (per-id boolean)

def mark_online(user_id):
    today = date.today().isoformat()
    redis.setbit(f"online:{today}", user_id, 1)

def is_online_today(user_id):
    return redis.getbit(f"online:{date.today().isoformat()}", user_id)

def count_online_today():
    return redis.bitcount(f"online:{date.today().isoformat()}")

100M users × 1 day = ~12MB.

Bitmap operations

BITOP AND result a b              # users online both days
BITOP OR result a b
BITOP XOR result a b
BITOP NOT result a

For analytics: cohort retention, etc.

Streams + windowed counts

# Insert events into stream
redis.xadd("clicks", {"page": "/x"}, maxlen=1_000_000)

# Recent count
end_id = "+"
start_id = f"{int((time.time() - 60) * 1000)}-0"
count = redis.xlen_stream_range("clicks", start_id, end_id)

Time-decayed scoring (zset)

def add_event(user_id):
    score = time.time()
    redis.zadd("recent", {f"{user_id}:{uuid4()}": score})

def trim_old(seconds_ago=3600):
    cutoff = time.time() - seconds_ago
    redis.zremrangebyscore("recent", 0, cutoff)

Trending feed pattern.

Sliding window rate

See rate limiting cheatsheet.

Common mistakes

  • Geo entries piling up (no TTL semantics).
  • Confusing PFCOUNT (approximate) with SCARD (exact).
  • Bitmap user_id huge → sparse → wasteful.
  • ZADD without GT/LT/NX/XX flags → may overwrite.
  • Mixing geo + non-geo in same zset.

Read this next

If you want my analytics recipes, they’re at rajpoot.dev .


Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .