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 .