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                   # → ~3

HyperLogLog 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-15

Bitmap 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 km

Geospatial 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
Knowledge is power.