Skip to content

feat(presence): add distributed presence service for the social layer#5

Merged
AlpNuhoglu merged 2 commits into
mainfrom
feat/distributed-presence-service
Jun 21, 2026
Merged

feat(presence): add distributed presence service for the social layer#5
AlpNuhoglu merged 2 commits into
mainfrom
feat/distributed-presence-service

Conversation

@AlpNuhoglu

Copy link
Copy Markdown
Owner

Introduce a standalone Presence Service as the foundation for friends, parties, invites, reconnect and notifications. Presence is modeled as a distributed system, not an online/offline boolean.

Design:

  • Redis is the source of truth: presence:{id} JSON (state, last_seen, connection_count) with a TTL. WS replicas heartbeat every 15s to refresh a 45s TTL, so presence self-heals after crashes/partitions with no explicit disconnect — a missing key is OFFLINE.
  • All mutations are Lua scripts (atomic read-modify-write), so any WS replica can update any player with no sticky sessions and no leader election — horizontally scalable.
  • Multi-device: connection_count keeps a player ONLINE until the last connection closes.
  • States OFFLINE/ONLINE/IN_QUEUE/IN_MATCH/AWAY with a deliberately permissive transition rule (only OFFLINE->IN_QUEUE/IN_MATCH rejected) so future reconnect/rematch/party flows need no state-machine changes.
  • Publishes PresenceOnline/Offline/StateChanged to NATS (events.presence, 15m retention) via the shared events.Publisher with trace propagation; consumers are optional. Reads are side-effect free.
  • Bulk friend lookup (POST /presence/friends) via a single Redis pipeline: O(N) commands, one round trip, no N+1.

WS gateway feeds presence over HTTP through an injected, optional notifier (nil = no-op) — the only coupling, no WS internals touched. Adds metrics, tracing spans, k8s manifest (2 replicas), Dockerfile, compose, and docs/presence.md. matchmaking is unchanged.

Introduce a standalone Presence Service as the foundation for friends,
parties, invites, reconnect and notifications. Presence is modeled as a
distributed system, not an online/offline boolean.

Design:
- Redis is the source of truth: presence:{id} JSON (state, last_seen,
  connection_count) with a TTL. WS replicas heartbeat every 15s to refresh a
  45s TTL, so presence self-heals after crashes/partitions with no explicit
  disconnect — a missing key is OFFLINE.
- All mutations are Lua scripts (atomic read-modify-write), so any WS replica
  can update any player with no sticky sessions and no leader election —
  horizontally scalable.
- Multi-device: connection_count keeps a player ONLINE until the last
  connection closes.
- States OFFLINE/ONLINE/IN_QUEUE/IN_MATCH/AWAY with a deliberately permissive
  transition rule (only OFFLINE->IN_QUEUE/IN_MATCH rejected) so future
  reconnect/rematch/party flows need no state-machine changes.
- Publishes PresenceOnline/Offline/StateChanged to NATS (events.presence,
  15m retention) via the shared events.Publisher with trace propagation;
  consumers are optional. Reads are side-effect free.
- Bulk friend lookup (POST /presence/friends) via a single Redis pipeline:
  O(N) commands, one round trip, no N+1.

WS gateway feeds presence over HTTP through an injected, optional notifier
(nil = no-op) — the only coupling, no WS internals touched. Adds metrics,
tracing spans, k8s manifest (2 replicas), Dockerfile, compose, and
docs/presence.md. matchmaking is unchanged.
Unexport the Repository mutating methods (connect/disconnect/heartbeat/
setState) so they no longer return the package-private mutation type through
an exported API; only Service calls them in-package. Add doc comments to the
State const block and the HTTPNotifier/NoopNotifier methods.
@AlpNuhoglu AlpNuhoglu merged commit 2858473 into main Jun 21, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant