Capabilities/Real-Time & Communication

Presence & Online Status

Distributed heartbeat tracking to show who is online in real time

presencereal-timeheartbeatdistributedstatus

Learning lens

Start with judgment, not a universal prescription.

This page is a learning model for understanding the capability. It does not claim exact production internals, exact costs, or an infinite-scale architecture.

Evidence

Conceptual pattern

0->1 default

Start with the simplest delivery path that meets latency needs before introducing durable fanout or global state.

Cost drivers

open connectionsfanout workpresence updatescross-region traffic

Watch for breakpoints

  • -connection count grows faster than app servers
  • -message fanout dominates request work
  • -presence state becomes stale or expensive

Learn first

Concepts behind this architecture

The Problem

Messaging apps, collaboration tools, and social platforms need to show whether users are currently online, idle, or offline. This seems simple but becomes a hard distributed systems problem at scale — millions of users constantly transitioning between states, each state change needing to fan out to potentially thousands of subscribers (friends, team members, channel participants). The system must handle network partitions, clock skew, and the inherent unreliability of client heartbeats while keeping the status reasonably accurate and the infrastructure cost manageable.

Architectural Approaches

Heartbeat with TTL-Based Expiry

Clients send periodic heartbeat signals (every 15-30 seconds). The server stores each user’s last heartbeat timestamp in a fast key-value store with a TTL slightly longer than the heartbeat interval. If the key expires, the user is considered offline. Simple, stateless, and easy to scale.

Pros

  • +Extremely simple to implement — just SET with TTL
  • +Stateless servers — any server can process any heartbeat
  • +Naturally handles crashes — TTL expires automatically
  • +Low per-user memory footprint (~100 bytes per user)

Cons

  • −Offline detection is delayed by the TTL window (30-60s)
  • −Heartbeats generate constant write traffic even for idle users
  • −No distinction between "offline" and "app backgrounded"
  • −Fan-out of status changes must be handled separately

Connection-Based Presence

Tie presence to the WebSocket or SSE connection lifecycle. When a connection opens, mark the user online; when it closes, mark them offline. Eliminates heartbeat overhead but requires reliable disconnect detection.

Pros

  • +Instant online/offline detection — no polling delay
  • +No heartbeat traffic overhead
  • +Naturally integrates with WebSocket infrastructure
  • +Can detect per-device presence (phone vs desktop)

Cons

  • −Depends on reliable disconnect detection (TCP FIN vs timeout)
  • −Network glitches cause false offline events
  • −Requires a connection registry tightly coupled to presence
  • −Server crashes orphan connections until TCP timeout

Hybrid: Connection + Heartbeat

Use connection events for instant transitions and heartbeats as a safety net. Mark online on connect, start a heartbeat timer. If heartbeats stop (but connection isn’t explicitly closed), transition to “idle” or “away” before marking offline. Most production systems use this approach.

Pros

  • +Fast detection on connect/disconnect
  • +Heartbeat catches silent disconnects and idle detection
  • +Supports rich status: online → idle → away → offline
  • +Resilient to partial failures

Cons

  • −More complex state machine per user
  • −Still has some heartbeat overhead (can be reduced with longer intervals)
  • −Must reconcile connection events with heartbeat state

Architecture

Loading diagram...
Presence & Online Status — High-level architecture

Key Components

Presence Worker

Processes heartbeat events, connection opens, and disconnects. Updates the presence store (Redis) and publishes status change events to the pub/sub bus. Runs as a stateless service behind the WebSocket gateways.

Presence Store (Redis)

Stores per-user presence state: status, last heartbeat timestamp, connected devices, and gateway ID. Uses Redis hashes with TTL for automatic expiry. Supports atomic operations for multi-device presence aggregation.

Status Fan-Out Service

Subscribes to status change events and determines which users need to be notified. Queries the social graph (friends, channel members) and pushes updates through the appropriate WebSocket gateways. Batches updates to avoid thundering herd on mass reconnects.

Presence Query API

REST/gRPC endpoint for bulk presence queries. Fetches the status of multiple users in a single call (e.g., “get status of all 50 people in this channel”). Uses Redis MGET for efficient batch reads.

Idle Detection

Client-side component that tracks user activity (mouse movement, key presses, touch events). Reports activity status to the server which transitions presence between active, idle, and away states.

Data Model

Loading diagram...
Presence & Online Status — Entity relationship diagram

Multi-Device Presence Aggregation

When a user has multiple devices (phone, laptop, tablet), presence must be aggregated:

Rules:

  • User is online if ANY device is active
  • User is idle if ALL devices are idle
  • User is offline if ALL devices are disconnected
  • User’s status shows the most active device state

Implementation: Store a set of connected devices per user in Redis (SADD/SREM). On any device event, compute the aggregate status:

  1. Device connects → SADD user:devices device_id, SET user:status online
  2. Device heartbeat → UPDATE user:device:{id}:last_seen
  3. Device disconnects → SREM user:devices device_id
  4. If device set empty → SET user:status offline
  5. If all devices idle → SET user:status idle

Use Redis transactions (MULTI/EXEC) to make aggregate computation atomic.

Fan-Out Optimization

The biggest scalability challenge is fan-out: when a user’s status changes, who needs to know?

Naive approach: Notify all friends/contacts. For a user with 500 friends, each status change triggers 500 push notifications. If 100K users come online in a minute (morning rush), that’s 50M notifications/minute.

Optimizations:

  • Lazy fan-out: Only notify users who currently have the status-changed user visible (open chat, visible friend list). Track “active subscriptions” rather than the full social graph.
  • Batching: Buffer status changes for 2-5 seconds and send batched updates. Reduces message count dramatically during mass online/offline events.
  • Tiered updates: Immediate notification for active conversations, batched for friend list, dropped for background contexts.
  • Bloom filter: Use a Bloom filter to quickly check if any online user is subscribed to updates for a given user, avoiding unnecessary fan-out queries.

WhatsApp famously limits presence to one-on-one chat views — you only see someone’s status when you’re actively in a conversation with them. This reduces fan-out from O(friends) to O(1).

Consistency vs Availability

Presence is an inherently eventually consistent system. Trade strict accuracy for availability and performance.

Acceptable inaccuracy: It’s okay if a user appears online for 30-60 seconds after closing the app. It’s NOT okay if a user appears offline while actively chatting.

Design principles:

  • Bias toward online: If uncertain, show online. False positives (showing online when offline) are less harmful than false negatives.
  • Exponential backoff on offline: Don’t immediately show offline on a missed heartbeat — wait for 2-3 missed intervals. Network is unreliable.
  • Local caching: Cache presence state on the client for 10-30 seconds. Don’t re-query on every screen load.
  • Graceful degradation: If the presence service is down, show “status unavailable” rather than “offline” for everyone.

Consistency across regions: For multi-region deployments, replicate presence data asynchronously. Accept that users in different regions may see slightly different presence states for a brief window.

Real-World Examples

WhatsApp

Shows “last seen” timestamps and “online” status only in 1:1 chat views. Presence is scoped to active conversations to minimize fan-out. Uses Erlang presence tracking tied to XMPP connection lifecycle.

Slack

Shows green dot (active), hollow circle (away), and custom status. Uses a hybrid heartbeat + connection approach with 30-second heartbeat intervals. Batches presence updates to channels.

Discord

Rich presence system showing online, idle, DND, invisible, and custom activity (playing a game, listening on Spotify). Presence is managed per-guild and distributed across Elixir nodes.

Microsoft Teams

Integrates with calendar and Outlook to automatically set status (In a meeting, Presenting). Uses Azure SignalR for real-time presence distribution across large organizations.

Architectural Tradeoffs

Heartbeat interval: 15s vs 30s vs 60s

Pros

  • +Shorter interval = faster offline detection
  • +Longer interval = less traffic and server load
  • +30s is the most common sweet spot

Cons

  • −15s: doubles write traffic vs 30s, marginal accuracy gain
  • −60s: user appears online for up to a minute after leaving
  • −Must tune per-platform (mobile needs longer to save battery)

Eager fan-out vs lazy subscription

Pros

  • +Eager: instant status updates for all contacts
  • +Lazy: dramatically lower infrastructure cost
  • +Lazy: naturally scales with active users, not total users

Cons

  • −Eager: O(friends) messages per status change
  • −Lazy: slight delay when opening a chat/contact list
  • −Lazy: more complex — must track active subscriptions

Redis TTL vs explicit offline events

Pros

  • +TTL: automatic cleanup, no leak risk
  • +Explicit: instant offline detection on graceful close
  • +Hybrid: explicit events + TTL safety net is most reliable

Cons

  • −TTL-only: delayed offline detection by TTL duration
  • −Explicit-only: missed events on crashes leak online ghosts
  • −Hybrid: more complex state management

Learn Presence & Online Status

Copy a learning context prompt for an AI assistant. It keeps assumptions visible and asks for tradeoffs, cost drivers, failure modes, and what to measure before adding complexity.

Explore More Capabilities