Build a Concurrent Device Session Manager


distributed-systems scalability reliability

System Design Deep Dive

Concurrent Device Session Manager

Enforce N-device limits across distributed servers without a race that lets the (N+1)th device slip through

⏱ 14 min read 📐 Advanced 🏗️ Distributed Systems

Think about a household sharing a streaming subscription. The plan says two screens at once - and most of the time, that rule means nothing, because nobody checks. But the moment a third person tries to press play while two streams are already running, the platform has to make a hard call: reject the request or silently kick one of the existing sessions. That decision, which sounds trivial when described in a sentence, conceals one of the trickiest distributed systems problems you will encounter in a product that has to work at global scale.

The difficulty comes from the distributed nature of streaming infrastructure. Any given user’s devices may connect to different regional API servers. There is no single node that has a complete, up-to-date picture of how many devices are actively streaming for a given account at this exact moment. Building a globally correct count under that constraint means accepting a coordination cost. Make that cost too high and every play request adds 50+ ms of latency. Make it too loose and you allow the N+1 device to slip through on a race window between two servers that both read a count of N-1 and both increment to N.

Session management becomes harder still when you account for reconnects. A device that loses connectivity briefly should be able to resume its session without being evicted and forced through the full authentication cycle again. But that same reconnect path is also the attack surface where a shared credential gets passed around a friend group. A robust design has to distinguish between a legitimate resume and a new device masquerading as an existing one, all within the same code path.

There are three core architectural challenges this post addresses. First: how do you count live sessions with atomic precision across N stateless API servers? Second: how do you define “live” in a system where clients can go dark silently - no TCP FIN, no HTTP teardown? Third: how do you enforce per-plan limits - where a Basic subscriber gets 1 screen and Premium gets 4 - without making the plan lookup a synchronous bottleneck on every play request?

Requirements and Constraints

Functional requirements:

  • Enforce concurrent device limits per subscription plan: Basic = 1 device, Standard = 2 devices, Premium = 4 devices
  • When a new device connects and the limit is already at capacity, evict the oldest active session (lowest heartbeat timestamp) and allow the new one
  • Track device identity via fingerprints (device ID + user agent + IP subnet) to distinguish unique devices
  • Handle reconnects idempotently - a device that lost connectivity and reconnects within the session TTL should refresh its existing session, not create a new one
  • Provide a session list endpoint so users can see and manually revoke sessions from any device
  • Support dynamic plan changes without requiring all sessions to reconnect

Non-functional requirements:

  • Sub-100ms enforcement latency at p99 - the check-and-admit decision must be fast enough to not degrade playback start time
  • 99.99% correctness - no N+1 slip-throughs under concurrent connection storms
  • Support 1 million concurrent active sessions across the platform
  • No single global coordinator - the design must survive the loss of any individual node without service degradation

Constraints:

  • Geo-distributed deployment across at least 3 regions
  • Sessions managed in Redis (not in the API server’s memory) to survive server restarts and horizontal scaling
  • Plan limits are stored in Postgres but must be cached to avoid database hits on every play request
  • The heartbeat interval is 30 seconds; a session with no heartbeat for 90 seconds is considered dead

High-Level Architecture

The system is composed of five major layers that coordinate to enforce session limits reliably.

Concurrent Device Session Manager Architecture

The API Gateway sits at the edge and routes all client traffic to the Session Service cluster. It performs request authentication via JWT validation and injects the verified user ID into downstream service calls. Critically, the gateway itself does not make session decisions - it only authenticates identity and forwards.

The Session Service is a horizontally scalable, stateless cluster. Every node is identical. When a device sends a play request, the Session Service performs the check-and-admit sequence: look up the user’s plan limit from the Plan Store cache, then execute an atomic Lua script against the Redis Session Registry that counts current sessions, evicts the oldest if needed, and registers the new session - all in a single round-trip. Because the logic runs inside Redis, it does not matter which Session Service node handles the request; the result is always consistent.

The Redis Session Registry is the system’s source of truth for session state. It uses a sorted set per user, keyed by session ID with the score set to the last heartbeat timestamp. The sorted set structure lets us retrieve the session count, find the oldest session (lowest score), and remove expired sessions all in O(log N) time. The Heartbeat Worker is a lightweight background process that runs on each client device and sends a keep-alive ping every 30 seconds. The Plan Store is Postgres for durability with a Redis cache layer that holds plan limits with a 60-second TTL.

Key Design Decision

The atomic Lua script in Redis is the entire correctness story. By pushing the count-check-evict-insert sequence into a single script that Redis executes serially (Redis is single-threaded for command execution), you eliminate the TOCTOU race that would otherwise let two simultaneous connections both read count=N-1 and both succeed when only one should.

The Session Registry

Every user’s active sessions live in a Redis sorted set at key sessions:{userId}. The score is the Unix timestamp (in milliseconds) of the most recent heartbeat received for that session. This means the member with the lowest score is always the stalest session - the one we evict when the limit is exceeded.

Premium Content

Unlock the full article along with everything else in the archive — all in one place.

In-depth analysis Expert insights Full archive access
Unlock Full Article