Module P-9·16 min read

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:

  1. GET session:{token} → deserialize JSON
  2. Modify the field in application memory
  3. 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.

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 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.

Sign in & Register

Discussion

0

Join the discussion

Loading comments...

© 2026 Jatin Jain Saraf (JJS). All rights reserved.