Redis Special Types
Redis offers specialized data types for specific use cases: HyperLogLog for cardinality estimation, Bitmaps for boolean arrays, and Geospatial indexes for location-based queries.
HyperLogLog
HyperLogLog is a probabilistic data structure for cardinality estimation—counting unique elements with minimal memory. It trades perfect accuracy for extreme space efficiency: ~12KB for billions of unique elements. Key characteristics: • Standard error: 0.81% • Fixed memory: ~12KB regardless of cardinality • Mergeable: combine multiple HLLs
HyperLogLog Commands
# Add elements
PFADD visitors:today "user:1" "user:2" "user:3"
PFADD visitors:today "user:1" # Duplicate (still counted once)
PFADD visitors:today "user:4" "user:5"
# Count unique elements (estimated)
PFCOUNT visitors:today # → ~5
# Multiple keys
PFCOUNT visitors:day1 visitors:day2 # Union count
# Merge HLLs
PFADD visitors:day1 "user:1" "user:2"
PFADD visitors:day2 "user:2" "user:3"
PFMERGE visitors:week visitors:day1 visitors:day2
PFCOUNT visitors:week # → ~3HyperLogLog Use Cases
# Unique Visitor Tracking
def track_visitor(page, visitor_id):
today = datetime.now().strftime("%Y-%m-%d")
PFADD(f"visitors:{page}:{today}", visitor_id)
def get_unique_visitors(page, date):
return PFCOUNT(f"visitors:{page}:{date}")
def get_weekly_uniques(page):
keys = [f"visitors:{page}:{day}" for day in last_7_days()]
temp_key = f"visitors:{page}:week"
PFMERGE(temp_key, *keys)
count = PFCOUNT(temp_key)
DEL(temp_key)
return count
# Unique Search Queries
def track_search_query(query):
month = datetime.now().strftime("%Y-%m")
PFADD(f"searches:{month}", query.lower())
def get_unique_searches(month):
return PFCOUNT(f"searches:{month}")Bitmaps
Bitmaps aren't a separate data type—they're string values treated as bit arrays. They enable extremely memory-efficient boolean operations across millions of elements. Key characteristics: • 1 bit per boolean value • 512MB = 4 billion bits • O(1) bit operations • Bitwise AND, OR, XOR, NOT across keys
Bitmap Commands
# Set individual bits
SETBIT logins:2024-01-15 1000 1 # User 1000 logged in
SETBIT logins:2024-01-15 1001 1 # User 1001 logged in
SETBIT logins:2024-01-15 1002 1
# Get bit value
GETBIT logins:2024-01-15 1000 # → 1
GETBIT logins:2024-01-15 9999 # → 0 (not set)
# Count set bits
BITCOUNT logins:2024-01-15 # Total users who logged in
BITCOUNT logins:2024-01-15 0 100 # In byte range 0-100
# Find first bit
BITPOS logins:2024-01-15 1 # Position of first '1'
BITPOS logins:2024-01-15 0 # Position of first '0'Bitmap Operations
# Bitwise AND (users who logged in BOTH days)
BITOP AND active:both logins:2024-01-15 logins:2024-01-16
BITCOUNT active:both
# Bitwise OR (users who logged in EITHER day)
BITOP OR active:any logins:2024-01-15 logins:2024-01-16
BITCOUNT active:any
# Bitwise XOR (users who logged in only one day)
BITOP XOR active:exclusive logins:2024-01-15 logins:2024-01-16
# Bitwise NOT
BITOP NOT inactive:2024-01-15 logins:2024-01-15Bitmap Use Cases
# 7-Day Retention Analysis
def record_login(user_id):
today = datetime.now().strftime("%Y-%m-%d")
SETBIT(f"logins:{today}", user_id, 1)
def calculate_retention(cohort_date, days=7):
cohort_key = f"logins:{cohort_date}"
retained = []
for i in range(1, days + 1):
check_date = cohort_date + timedelta(days=i)
check_key = f"logins:{check_date}"
result_key = f"temp:retention:{i}"
BITOP("AND", result_key, cohort_key, check_key)
retained.append(BITCOUNT(result_key))
DEL(result_key)
cohort_size = BITCOUNT(cohort_key)
return [r / cohort_size for r in retained]
# Feature Flags per User
def set_feature(user_id, feature_index, enabled):
SETBIT(f"features:{user_id}", feature_index, 1 if enabled else 0)
def has_feature(user_id, feature_index):
return GETBIT(f"features:{user_id}", feature_index) == 1
# Online Status (1 bit per user)
def set_online(user_id):
SETBIT("online:users", user_id, 1)
def set_offline(user_id):
SETBIT("online:users", user_id, 0)
def count_online():
return BITCOUNT("online:users")Geospatial Indexes
Redis Geo is built on Sorted Sets, storing longitude/latitude pairs and enabling location-based queries like radius searches and distance calculations. Key characteristics: • Based on Sorted Sets (geohash as score) • Radius queries in km or miles • Distance calculations • Geohash encoding/decoding
Geospatial Commands
# Add locations (longitude, latitude, name)
GEOADD restaurants -73.935242 40.730610 "Joe's Pizza"
GEOADD restaurants -73.980000 40.750000 "Times Square Diner"
GEOADD restaurants -73.990000 40.720000 "Chinatown Noodles"
# Get coordinates
GEOPOS restaurants "Joe's Pizza"
# → [[-73.935242, 40.730610]]
# Calculate distance
GEODIST restaurants "Joe's Pizza" "Times Square Diner" km
GEODIST restaurants "Joe's Pizza" "Times Square Diner" mi
# Get geohash
GEOHASH restaurants "Joe's Pizza"
# Remove (uses ZREM since it's a Sorted Set)
ZREM restaurants "Chinatown Noodles"Radius Searches
# Search by coordinates
GEORADIUS restaurants -73.950 40.740 5 km
GEORADIUS restaurants -73.950 40.740 5 km WITHDIST
GEORADIUS restaurants -73.950 40.740 5 km WITHDIST WITHCOORD
GEORADIUS restaurants -73.950 40.740 5 km WITHDIST COUNT 10 ASC
# Search by member location
GEORADIUSBYMEMBER restaurants "Joe's Pizza" 3 km WITHDIST
# Newer commands (Redis 6.2+)
GEOSEARCH restaurants FROMMEMBER "Joe's Pizza" BYRADIUS 5 km
GEOSEARCH restaurants FROMLONLAT -73.95 40.74 BYBOX 10 10 km
GEOSEARCHSTORE results restaurants FROMMEMBER "Joe's Pizza" BYRADIUS 5 kmGeospatial Use Cases
# Find Nearby Restaurants
def find_nearby(lat, lon, radius_km=5, limit=20):
return GEORADIUS(
"restaurants",
lon, lat,
radius_km, "km",
WITHDIST=True,
WITHCOORD=True,
COUNT=limit,
ASC=True
)
# Driver Matching for Ride-sharing
def update_driver_location(driver_id, lat, lon):
GEOADD("active_drivers", lon, lat, driver_id)
EXPIRE("active_drivers", 300) # Clear inactive
def find_nearest_drivers(pickup_lat, pickup_lon, count=5):
return GEORADIUS(
"active_drivers",
pickup_lon, pickup_lat,
10, "km",
WITHDIST=True,
COUNT=count,
ASC=True
)
def remove_driver(driver_id):
ZREM("active_drivers", driver_id)
# Delivery Zone Check
def is_in_delivery_zone(restaurant_lat, restaurant_lon,
customer_lat, customer_lon, max_km=8):
# Add temp location
temp_key = f"temp:check:{uuid()}"
GEOADD(temp_key, restaurant_lon, restaurant_lat, "restaurant")
GEOADD(temp_key, customer_lon, customer_lat, "customer")
distance = GEODIST(temp_key, "restaurant", "customer", "km")
DEL(temp_key)
return float(distance) <= max_km