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.
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
text
Individual field updates are atomic:
text
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).
typescript
Session Expiry: Idle Timeout vs Absolute Timeout
Idle Timeout (Sliding Expiry)
The session expires if unused for N minutes. Activity resets the timer.
typescript
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:
typescript
typescript
Combining Both
Most security-conscious systems use both:
Idle timeout: 30 minutes of inactivity
Absolute timeout: 24 hours regardless of activity
typescript
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
typescript
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.
text
Fix: Always read sessions from the primary.
typescript
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:
typescript
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.
HttpOnly — JavaScript cannot access the cookie (XSS protection)
Secure — only sent over HTTPS
SameSite=Strict — CSRF protection
Summary
Use a Hash for session data — atomic per-field updates, self-documenting, no race conditions
Idle timeout via EXPIRE on each request; absolute timeout via a stored expiry field
Multi-device sessions: track token Set per user (user:sessions:{userId}); revoke by DEL session Hash + SREM from Set
Always read sessions from the primary — replica lag causes silent authentication failures
Secure tokens: 256-bit cryptographically random, stored in HttpOnly; Secure; SameSite=Strict cookies
Next: P-10 — Connection Pooling and Client Configuration — TCP connection overhead, ioredis pool sizing, reconnection strategies, and health check patterns for production clients.
Knowledge Check
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.