Session Storage and Leaderboard Patterns
Use ElastiCache to offload HTTP session state from your application servers and implement real-time leaderboards with Redis sorted sets.
Session Storage and Leaderboard Patterns is a free AWS Solutions Architect lesson on CoddyKit — lesson 4 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the AWS Solutions Architect learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
The Problem with Server-Side Sessions
Traditional web applications store session data in memory on the server. This works with a single server but breaks when you scale horizontally — if a user's subsequent request is routed to a different EC2 instance, that instance has no knowledge of the user's session and the user is logged out. Sticky sessions (session affinity in the load balancer) solve this partially but reduce the effectiveness of load balancing. The scalable solution is to move session state to a shared, low-latency store accessible by all instances — which is exactly what ElastiCache Redis provides.
Redis for Session Storage
Storing sessions in Redis gives you: sub-millisecond session reads across all application servers, built-in TTL for automatic session expiry, atomic session updates to prevent race conditions, and the ability to immediately invalidate a session by deleting the key. The application stores the session ID in a cookie; on each request it looks up the session ID in Redis to retrieve the session data. All application servers share the same Redis, so any server can handle any user's request.
# Session storage with Redis (Python Flask example)
import redis, json, uuid
from datetime import timedelta
redis_client = redis.Redis(host='prod-redis-primary', port=6379)
SESSION_TTL = int(timedelta(hours=8).total_seconds())
def create_session(user_id):
session_id = str(uuid.uuid4())
session_data = {'user_id': user_id, 'logged_in': True}
redis_client.setex(f'session:{session_id}', SESSION_TTL, json.dumps(session_data))
return session_id
def get_session(session_id):
data = redis_client.get(f'session:{session_id}')
return json.loads(data) if data else NoneSession TTL and Sliding Expiry
A fixed TTL means the session expires N seconds after creation regardless of activity. A sliding TTL (extending expiry on each access) is more user-friendly — the session expires N seconds after the last access. In Redis, implement sliding TTL by calling EXPIRE (or EXPIREAT) on the session key on every successful session read to reset the expiry countdown. This ensures active users are never unexpectedly logged out, while inactive sessions expire automatically, freeing memory.
# Sliding TTL session implementation
def get_session_with_sliding_ttl(session_id, redis_client, ttl_seconds=1800):
session_key = f'session:{session_id}'
# Pipeline: GET + EXPIRE in one round trip
pipe = redis_client.pipeline()
pipe.get(session_key)
pipe.expire(session_key, ttl_seconds) # Reset TTL on access
results = pipe.execute()
data = results[0]
if data:
return json.loads(data)
return None # Session expired or not foundShopping Cart in Redis
An e-commerce shopping cart is a natural fit for Redis. Each cart is stored as a Redis Hash where the field is the product SKU and the value is the quantity. Hash operations like HINCRBY and HDEL allow atomic updates without retrieving and rewriting the entire cart. Combined with a TTL (to expire abandoned carts after 24 hours), Redis provides a fast, persistent cart store without the overhead of a relational database for every add-to-cart event.
# Shopping cart operations using Redis Hash
cart_key = f'cart:{user_id}'
# Add item (or increase quantity)
# HINCRBY cart:user42 SKU-001 2
redis_client.hincrby(cart_key, 'SKU-001', 2)
# Remove item
# HDEL cart:user42 SKU-001
redis_client.hdel(cart_key, 'SKU-001')
# Get all items in cart
# HGETALL cart:user42
cart = redis_client.hgetall(cart_key) # {b'SKU-001': b'2', b'SKU-002': b'1'}
# Set TTL for cart abandonment (24 hours)
redis_client.expire(cart_key, 86400)Leaderboard Architecture
A real-time leaderboard is a classic Redis use case enabled by Sorted Sets (ZSETs). Every player entry has a score; the sorted set maintains members in ascending score order at all times. Leaderboard queries (top N players, player rank, players in a score range) are O(log n) or O(log n + m) — extremely fast even for millions of players. Redis sorted sets are the foundation of many gaming, fitness, and social ranking features without needing a complex database query or recalculating rank on every page view.
# Real-time leaderboard with Redis Sorted Set
# Add or update a player's score
# ZADD game:weekly:leaderboard 15750 'player:alice'
redis_client.zadd('game:weekly:leaderboard', {'player:alice': 15750})
# Increment score (atomic)
# ZINCRBY game:weekly:leaderboard 500 'player:alice'
redis_client.zincrby('game:weekly:leaderboard', 500, 'player:alice')
# Get top 10 players (highest scores first)
# ZREVRANGE game:weekly:leaderboard 0 9 WITHSCORES
top_10 = redis_client.zrevrange('game:weekly:leaderboard', 0, 9, withscores=True)Player Rank and Nearby Players
Two common leaderboard features beyond 'show top 10' are show a player's rank and show players near a given player. Both are trivial with Redis sorted sets. ZREVRANK returns the 0-indexed rank of a player in descending score order. To show 5 players above and below a player, get their rank, then use ZREVRANGE from rank-5 to rank+5. This gives a personalised leaderboard view in two Redis commands — no complex SQL window functions needed.
# Get Alice's rank (0-indexed, so add 1 for display)
# ZREVRANK game:weekly:leaderboard 'player:alice'
rank = redis_client.zrevrank('game:weekly:leaderboard', 'player:alice')
print(f'Alice is rank #{rank + 1}')
# Get 5 players above and below Alice
start = max(0, rank - 5)
end = rank + 5
nearby = redis_client.zrevrange(
'game:weekly:leaderboard', start, end, withscores=True
)
print('Players near Alice:', nearby)Rate Limiting with Redis
Rate limiting (restricting how many requests a client can make in a time window) is another high-value Redis use case. The sliding window algorithm uses a sorted set where each member is a request timestamp. On each request: remove members older than the window, count remaining members, reject if count exceeds limit, add new timestamp. This implements precise sliding window rate limiting with millisecond resolution — far more accurate than fixed-window counters and without database overhead.
# Sliding window rate limiter (100 requests per 60 seconds)
import time
def is_rate_limited(user_id, redis_client, limit=100, window_seconds=60):
key = f'ratelimit:{user_id}'
now = time.time()
window_start = now - window_seconds
pipe = redis_client.pipeline()
pipe.zremrangebyscore(key, '-inf', window_start) # Remove old
pipe.zcard(key) # Count current
pipe.zadd(key, {str(now): now}) # Add this request
pipe.expire(key, window_seconds)
results = pipe.execute()
request_count = results[1]
return request_count >= limit # True = rate limitedDistributed Locking with Redis
Distributed locks coordinate exclusive access to a shared resource across multiple application servers. Redis's SET key value NX EX ttl command provides atomic lock acquisition — it sets the key only if it does not exist (NX = Not eXists) and sets a TTL to prevent deadlocks if the holder crashes. When the operation completes, the holder deletes the key. The Redlock algorithm (using multiple Redis nodes for quorum) provides a more robust distributed lock, though it adds complexity. For most use cases, a single Redis node lock is sufficient.
# Distributed lock with Redis SET NX EX
import uuid
def acquire_lock(redis_client, resource, ttl_seconds=30):
lock_id = str(uuid.uuid4()) # Unique ID to identify this lock holder
key = f'lock:{resource}'
acquired = redis_client.set(key, lock_id, nx=True, ex=ttl_seconds)
return lock_id if acquired else None
def release_lock(redis_client, resource, lock_id):
key = f'lock:{resource}'
# Only delete if we still own the lock (Lua script for atomicity)
lua = 'if redis.call("get",KEYS[1])==ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end'
redis_client.eval(lua, 1, key, lock_id)Session Storage: ElastiCache vs DynamoDB
Both ElastiCache Redis and DynamoDB can store session data, but they have different trade-offs. ElastiCache Redis: microsecond latency, in-memory (volatile unless persistence enabled), simpler data model, requires VPC. DynamoDB: single-digit millisecond latency (DAX can match Redis), fully managed with no cluster to maintain, durable by default, accessible globally with Global Tables, serverless with on-demand capacity. For the SAA-C03 exam: if the question emphasises microsecond latency or complex in-memory operations, choose Redis. If it emphasises durability, serverless, or global scale, consider DynamoDB.
Geospatial Indexing with Redis
Redis has a built-in geospatial data type (GEO commands) that stores latitude/longitude coordinates and enables proximity queries. Using GEOADD, GEODIST, and GEORADIUS (now GEOSEARCH in Redis 6.2), you can find all locations within a given radius of a point in O(n + log n) time. Use cases: find nearby drivers (ride-sharing), find restaurants within 5 km, sort search results by distance. This avoids a separate geospatial database and keeps location queries at in-memory speeds.
# Store driver locations
# GEOADD drivers 13.361389 38.115556 'driver:001'
# GEOADD drivers 15.087269 37.502669 'driver:002'
# Find all drivers within 10 km of a point
# GEOSEARCH drivers FROMLONLAT 13.5 38.1 BYRADIUS 10 km ASC COUNT 5 WITHCOORD
# Result: sorted list of driver IDs within 10 km with coordinatesHyperLogLog for Unique Visitor Counting
A HyperLogLog is a probabilistic data structure that estimates the count of unique elements in a set using a fixed amount of memory (12 KB in Redis) regardless of how many unique items are added. It provides approximately 0.81% standard error. Use PFADD to add elements and PFCOUNT to get the estimate. This is perfect for counting unique daily active users, unique page views, or unique IP addresses when exact counts are not required and memory efficiency matters. Storing millions of unique user IDs as a Redis Set would consume GBs; HyperLogLog uses 12 KB.
# Count unique daily visitors using HyperLogLog
date = '2024-01-15'
hll_key = f'unique_visitors:{date}'
# Track a visitor (PFADD is idempotent for the same user)
# PFADD unique_visitors:2024-01-15 'user:12345'
redis_client.pfadd(hll_key, 'user:12345')
redis_client.pfadd(hll_key, 'user:67890')
redis_client.pfadd(hll_key, 'user:12345') # Duplicate — not counted again
# Get estimated unique visitor count
# PFCOUNT unique_visitors:2024-01-15
count = redis_client.pfcount(hll_key)
print(f'Unique visitors today (estimate): {count}')Quick Check
Test your understanding of AWS Solutions Architect (SAA-C03) concepts from this lesson.
Lesson Recap
In this lesson you learned: Redis session storage enables stateless horizontal scaling by giving all instances access to shared session state with sub-millisecond latency, Redis sorted sets power real-time leaderboards with O(log n) rank queries, and specialized Redis data types (HyperLogLog for unique counts, GEO for proximity, distributed locks) solve common architectural problems efficiently. This completes the Caching with ElastiCache course — next we explore High Availability and Fault-Tolerant Architectures.
Frequently asked questions
Is the “Session Storage and Leaderboard Patterns” lesson free?
Yes — the full text of “Session Storage and Leaderboard Patterns” is free to read here on the web, and the AWS Solutions Architect course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the AWS Solutions Architect course, upgrade to CoddyKit PRO.
What will I learn in “Session Storage and Leaderboard Patterns”?
Use ElastiCache to offload HTTP session state from your application servers and implement real-time leaderboards with Redis sorted sets. You practise AWS Solutions Architect with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start AWS Solutions Architect?
No prior experience is required. AWS Solutions Architect on CoddyKit is structured for beginners through advanced learners; this is — lesson 4 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Session Storage and Leaderboard Patterns” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this AWS Solutions Architect lesson?
Yes. Every AWS Solutions Architect lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- Redis vs Memcached: Choosing the Right Engine
- ElastiCache Redis Replication Groups and Cluster Mode
- Caching Strategies: Lazy Loading and Write-Through
- Session Storage and Leaderboard Patterns