Storing sessions as Hashes vs JSON strings, sliding expiry with EXPIRE on each request, session invalidation and logout, multi-device session tracking, and the consistency trade-offs when reading sessions from replicas.
P-9 — Session Management Patterns
Who this module is for: You store user sessions in Redis — or are planning to — and want to implement them correctly. This covers session structure (Hash vs JSON string), sliding expiry, concurrent device management, and the read-from-replica consistency trap that silently logs users out.
Why Redis for Sessions
HTTP is stateless. Sessions provide continuity: a session token in a cookie maps to server-side state (user ID, permissions, preferences). The session store must be:
- Fast — checked on every request
- Shared — accessible from every application server instance
- Expirable — sessions must auto-expire when idle or after a fixed duration
Redis satisfies all three. A 1ms session lookup is imperceptible. Every application server connects to the same Redis instance. TTL handles expiry automatically.
The alternative — database-backed sessions (PostgreSQL, MySQL) — works but adds a database query to every request, and relational databases are optimized for complex queries, not millions of simple ID lookups.
Session Structure: Hash vs JSON String
Option A: JSON String
SET session:{token} '{"userId":1001,"email":"j@example.com","role":"engineer","permissions":["read","write"]}' EX 3600
Simple. One key, one value. But updating a single field requires:
GET session:{token}→ deserialize JSON- Modify the field in application memory
SET session:{token} {updated JSON}→ re-serialize and overwrite
Under concurrent requests (two requests updating different session fields simultaneously), one overwrites the other's changes. Race condition.
Option B: Redis Hash
Individual field updates are atomic:
Multiple fields can be updated without touching others. No race condition for independent field updates.
The trade-off: Hashes require one more key + the field name overhead. For sessions with 5–10 fields, the overhead is minimal. For sessions storing complex nested objects, you may need to serialize sub-objects as JSON strings within Hash fields.
Recommendation: Use a Hash for session storage. It enables atomic per-field updates and makes session data self-describing (HGETALL returns field names, not just a JSON blob).
Session Expiry: Idle Timeout vs Absolute Timeout
Idle Timeout (Sliding Expiry)
The session expires if unused for N minutes. Activity resets the timer.
Call touchSession on every authenticated request. If it returns false, the session expired and the user should be logged out.
Absolute Timeout
The session expires at a fixed time after creation, regardless of activity. Implement by storing the absolute expiry timestamp in the session Hash and checking it on every access:
Combining Both
Most security-conscious systems use both:
- Idle timeout: 30 minutes of inactivity
- Absolute timeout: 24 hours regardless of activity
Multi-Device Session Management
Users log in from multiple devices. Each device gets its own session token. You need to:
- List all active sessions for a user
- Revoke a specific session (logout from one device)
- Revoke all sessions (logout from all devices, e.g., after password change)
Track Sessions per User with a Set
Memory cleanup: The user sessions Set may contain tokens of expired sessions (the session Hash expired but the token remains in the Set). The getActiveSessions function handles this by checking if the Hash exists, but the Set grows over time. Add a periodic cleanup or use SSCAN + EXISTS to prune stale tokens.
The Read-from-Replica Consistency Trap
Many Redis setups route read commands to replicas to reduce primary load. Session reads are a common target.
The problem: Replication in Redis is asynchronous. When a session is created or updated on the primary, the change propagates to replicas with a small delay (typically < 1ms, but up to seconds under load or network issues).
If a request is authenticated (session created on primary), and the next request reads the session from a replica before replication completes, the session lookup returns nil — the user appears logged out.
Fix: Always read sessions from the primary.
For most session workloads, the primary handles the load comfortably — sessions are a small fraction of total Redis traffic.
Session Token Security
This is not Redis-specific, but engineers implementing Redis sessions often get this wrong:
Use a cryptographically secure random token:
Do not use UUIDs (v4 UUIDs are random but some implementations have flaws; 122 bits of entropy vs 256).
Do not use incrementing integers or predictable patterns.
Do not sign session tokens with HMAC and put user data in the token unless you understand session forgery risks.
Set the cookie correctly:
Set-Cookie: session={token}; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=3600
HttpOnly— JavaScript cannot access the cookie (XSS protection)Secure— only sent over HTTPSSameSite=Strict— CSRF protection
Summary
- Use a Hash for session data — atomic per-field updates, self-documenting, no race conditions
- Idle timeout via
EXPIREon each request; absolute timeout via a stored expiry field - Multi-device sessions: track token Set per user (
user:sessions:{userId}); revoke byDELsession Hash +SREMfrom Set - Always read sessions from the primary — replica lag causes silent authentication failures
- Secure tokens: 256-bit cryptographically random, stored in
HttpOnly; Secure; SameSite=Strictcookies
Next: P-10 — Connection Pooling and Client Configuration — TCP connection overhead, ioredis pool sizing, reconnection strategies, and health check patterns for production clients.
A team decides to store user sessions as JSON strings in Redis (SET session:123 '{"userId":1, "lastSeen":170000}'). They have an endpoint that updates the lastSeen timestamp and another concurrent endpoint that adds a new item to a cart array within the same session. What is the fundamental risk of this approach compared to using a Redis Hash?
An application enforces both an "idle timeout" (30 minutes of inactivity) and an "absolute timeout" (24 hours maximum session life). Which implementation strategy is correct and most efficient?
To improve read performance, an engineering team configures their Node.js application to read session data from Redis read-replicas, while continuing to write new sessions to the primary. Shortly after, users report occasionally being "logged out" immediately after successfully logging in. What is the cause?
Test your knowledge with more question sets
Sign in to access a wider variety of questions and get notified when new practice sets are added to this module.
Sign in & RegisterDiscussion
0Join the discussion