A dispatcher must know whether a driver is actually available before creating a video room and issuing a scoped token. That constraint changes the usual push-versus-polling answer: use a realtime channel for presence transitions, keep polling as reconciliation, and treat the database as the authority. A channel alone is fast but can preserve a false online state after a broken connection; polling alone makes presence accuracy depend on an interval.
TL;DR: For a small logistics SaaS, the useful comparison is not request count versus socket count. It is the cost of being wrong about presence. Send compact, versioned presence events over a persistent channel, expire leases on the server, and poll a snapshot after reconnect or suspected gaps. Create the video-room token only from a fresh authoritative lease, never from the browser's last event.
Should a small SaaS use a realtime channel or polling for notification delivery?
An in-app notification such as "shipment assigned" can arrive late and still be useful. "Driver ready for video" is different. It controls whether the dispatcher should expose a join action and whether the server should mint a token scoped to one room, one participant, and a short validity window. A stale green dot can start a workflow that has no participant at the other end.
The simple design is periodic polling: each visible dispatcher tab requests the current state, then replaces its local view. It is easy to reason about because every successful response is a snapshot. Its weakness is structural. If the interval is long, departure detection is late; if it is short, quiet tabs keep generating work. Background-tab scheduling and temporary network loss also mean the interval configured in code is not the same as the interval observed by the server.
A persistent channel changes the shape of the work. The server sends only transitions, so an idle user does not require repeated notification reads. Yet delivery speed does not prove current presence. A client can miss events while disconnected, receive an older event after a newer snapshot, or keep an "online" value after the transport disappears without a clean close.
Fast can still be wrong.
That was the failed assumption in the first pass of this design: transport connected was treated as user present. They are separate states. WebRTC itself exposes several connection-related states, including ICE connection state and peer connection state; none of those states establishes the business meaning of "a driver is ready for a dispatcher to call." The application still needs an explicit, expiring presence record.
The experiment: optimize the wrong-state window
I would evaluate the two designs with a deterministic disconnect experiment, not a synthetic message-throughput contest. Use 2 tabs: one dispatcher and one driver. Mark the driver ready, then interrupt the driver's network without allowing a graceful sign-out. Record the time until the dispatcher UI removes the join action. Run the sequence 3 times for each condition: reconnect normally, hide the dispatcher tab, and deliberately drop 1 transition. These are test parameters, not claimed benchmark results; the observed values belong to the deployment being evaluated.
This produces a practical decision metric: the wrong-state window, measured from the real readiness change until every active observer shows the authoritative state. The test should also count snapshot reads, transition messages, concurrent connections, reconnect attempts, and token-creation rejections. Those quantities reveal operational load without turning a changeable infrastructure price into the argument.
Polling bounds the expected stale window by its actual schedule plus response time. A realtime channel can shorten the normal path, but its worst case is unbounded unless the server expires presence and clients reconcile. That is why I would choose a hybrid even for a small deployment. It adds one mechanism, but each mechanism has a narrow job: push carries hints quickly; polling repairs uncertainty.
The trade-off is real. The hybrid requires connection authentication, lease renewal, reconnect control, version tracking, snapshot repair, and two delivery paths in tests. Its limitation is operational complexity: a team without reliable connection telemetry may understand a plain polling loop better and recover it faster. For low-concurrency back-office notifications with no presence-dependent action, choose polling and keep the system smaller. For a video-room control whose validity depends on current presence, accept the extra machinery only after defining lease expiry and reconciliation behavior.
No benchmark number belongs here without traffic and failure data from the actual system. Start with a service-level target derived from the workflow. If dispatch can tolerate a driver appearing ready for several seconds after losing connectivity, the lease and reconciliation schedule can reflect that. If the join action has a tighter safety requirement, verify readiness again when issuing the token instead of driving the intervals toward zero.
A small state machine beats two independent data paths
The browser should apply snapshots and events through the same reducer. Give each server-side presence mutation a monotonically increasing version within the relevant scope. An event older than the local version is ignored; a detected gap triggers a snapshot. Reconnect always triggers a snapshot because the client cannot infer what happened while it was away.
type Presence = {
subjectId: string;
roomId: string;
ready: boolean;
version: number;
leaseExpiresAt: string;
};
type LocalState = {
records: Map<string, Presence>;
lastVersion: number;
needsSnapshot: boolean;
};
export function applyPresence(
state: LocalState,
incoming: Presence
): LocalState {
if (incoming.version <= state.lastVersion) return state;
const hasGap = incoming.version > state.lastVersion + 1;
const records = new Map(state.records);
records.set(incoming.subjectId, incoming);
return {
records,
lastVersion: incoming.version,
needsSnapshot: state.needsSnapshot || hasGap
};
}
The focused example deliberately does not open a WebSocket or prescribe an endpoint. Transport choice should not leak into the state rule. Server-sent events, a WebSocket, or another ordered channel can carry the same envelope, provided the implementation defines authentication, ordering scope, reconnect behavior, and backpressure.
One reducer. Two inputs.
A snapshot response must include its own version. Apply it atomically, reset needsSnapshot, and resume consuming events after that version. There is a race if events arrive during the snapshot request, so the client either buffers them and reapplies newer versions or reconnects with a cursor that the server understands. Pick one rule and test it. Hand-waving around this boundary is how a nominally realtime UI becomes confidently stale.
Room-token issuance stays on the server. The request identifies the intended room and participant; the server checks the current lease and authorization, then returns a narrowly scoped credential only if both are valid. A UI presence record may decide whether to show the button, but it must not authorize the action.
Failure policy, deployment, and observability
The server needs an explicit lifecycle for presence: acquire a lease, renew it while the application is ready, mark it unavailable on a clean exit when possible, and expire it when renewals stop. A process-local map is insufficient when requests or channel connections can land on different instances. Store the authoritative lease where all instances see consistent updates, and publish transitions only after the state change is accepted.
Deployments create ordinary disconnects. Add jitter to reconnect attempts so a rolling restart does not make every client retry on the same beat. Cap retries, surface a degraded indicator, and reconcile after connectivity returns. During degradation, the dispatcher can still read durable shipment notifications, but the video join action should follow the freshness rule. Fail closed for token creation.
The dashboard I want is small:
- wrong-state-window percentiles from controlled probes;
- active leases compared with active channel connections;
- event-version gaps and snapshot repairs;
- reconnect rate by client release;
- scoped-token denials split by expired presence and authorization failure.
Keep identifiers out of high-cardinality metric labels. Trace a sampled transition with a correlation identifier, and log lease version, room scope, and outcome using the system's retention policy. Message contents and credentials do not belong in logs.
This architecture also contains cost. Durable notification rows are fetched when the inbox opens or reconciliation runs; presence transitions are ephemeral and compact. Video media does not pass through the notification channel. WebRTC defines browser interfaces for peer connections and media exchange, but the signaling used to arrange a connection is outside the specification. That separation is useful: notification delivery, application signaling, presence authority, and media connection state should remain distinct even if one transport happens to carry more than one message type.
What should you measure before copying this choice?
First, measure workflow tolerance. How long may a departed driver remain callable, and what should the dispatcher see when freshness is unknown? Then measure actual concurrency, readiness transition rate, disconnect frequency, snapshot size, and the number of open tabs per account. Those inputs determine whether a persistent channel earns its extra connection lifecycle.
For a low-concurrency internal tool where delayed notification delivery is harmless, polling may remain the smaller system. Use conditional requests where the stack supports them, stop aggressive refresh when the view is irrelevant, and still verify authorization at action time. Do not add channel operations merely to remove a modest number of reads.
For the logistics video-room case, presence drives a consequential control. Push plus lease expiry and snapshot repair is the defensible choice because it minimizes normal delivery delay while bounding uncertainty after failure. The decision should be revisited if probes show that repair traffic, connection churn, or operational burden exceeds the value of a shorter wrong-state window.
The final acceptance test is blunt: cut connectivity at awkward moments and confirm that no stale UI state can produce an unauthorized or incorrectly scoped room credential. Fast notifications are pleasant. Correct presence is the requirement.
Top comments (0)