Realtime delivery
Three clients send messages through one server. Choose how they receive updates, make the network slow or unreliable, unplug a client and plug it back in, and watch what arrives, when, and what it cost. This is a local simulation: no real network or server is involved.
- Polling vs push
- Long polling
- Message ordering
- Replay buffer
- Reconnect cursor
- Deduplication
Like the caching experiment, this is a discrete-event simulation: requests, responses, frames, drops and reconnects are events on a simulated clock, processed in order. Playback speed changes how fast you watch, never the results.
The three transports
- Polling: every interval, each client sends
GET ?after=cursorand the server answers immediately, usually with nothing. A message waits on average half an interval before anyone asks for it. Sending is a separatePOST. - Long polling: the client keeps one request open; the server answers as soon as something newer exists (or after 20 s) and the client immediately opens the next one. Latency is close to push, at one request per batch received.
- Push (WebSocket-style):one connection opened with an HTTP upgrade (one round trip). The client sends a "resume after my cursor" frame, the server replays newer buffered messages, then forwards new messages as frames.
Network and connection loss
- Each one-way transmission takes the delay ± jitter. The value is derived from the transmission's identity, so all three transports see comparable conditions with the same seed.
- A push connection is ordered: frames never overtake each other (like TCP). Separate HTTP requests are independent and can arrive out of order, which is how overlapping polls create duplicates.
- Loss means a dropped connection. On an open connection, lost packets are retransmitted by TCP, so messages are delayed rather than lost. When a connection drops (spontaneously, or via Disconnect), everything in flight on it in both directions is lost.
- After a spontaneous drop the client retries after the reconnect delay. A manual disconnect stays offline until you press Reconnect.
- Simplification: both ends learn about a drop instantly. Real systems detect dead connections with heartbeats and timeouts, which adds delay.
Application-level reliability (not provided by WebSockets)
WebSockets and HTTP move bytes; they don't replay what a client missed, remove duplicates or guarantee delivery across reconnects. In this experiment, those come from the simulated application:
- Server-assigned ids: the server numbers every message (#1, #2, …). That number is the global order every client sees.
- Cursor: each client remembers the highest id it has processed and sends it when polling or resuming.
- Bounded replay buffer: the server keeps only the last N messages. A client returning with an older cursor gets an explicit gap notice, shown as a warning, for messages it can no longer recover.
- Deduplication: clients ignore ids at or below their cursor; the server ignores a resent message it has already stored (identified by client and per-client sequence number).
- Outbox and acks: a sent message stays in the client's outbox until the server confirms it, and is resent after a reconnect.
Under the hood
The model is plain TypeScript with no UI code, covered by unit tests for polling timing, push latency, server ordering, replay after reconnect, buffer exhaustion, client and server deduplication, loss in transit, repeatability and reset. The comparison table runs the model headlessly for each transport with the same seed.
Tradeoffs it shows
- Polling is simple and works everywhere, but trades latency (up to one interval) for requests, most of them empty. Shorter intervals cut latency and multiply requests.
- Long polling gets near-push latency over plain HTTP, but pays a request per batch and a reconnect gap after each response.
- Push has the lowest latency and almost no idle traffic, but needs long-lived connections, which affects load balancers, proxies and server memory.
- Replay buffer size trades memory for how long a client can be away and still catch up.
What a real implementation would also need
- Durable storage for messages (this server forgets everything on reset) and an agreed retention policy.
- Heartbeats or pings to detect dead connections, and reconnect backoff with jitter so thousands of clients don't reconnect at once.
- Authentication and authorisation on connect and on every message, plus rate limiting.
- Multiple server instances: shared ordering (or per-room ordering), a pub/sub layer between instances, and sticky or resumable sessions.
- Backpressure for slow clients, message size limits, and a way to resync a client whose gap is too large (e.g. reload recent history from the database).
- Ordering is total here because there is one server; distributed systems usually settle for ordering per conversation or partition.
Like the rest of the lab, this is a simplified model for intuition, not a benchmark of any real WebSocket or HTTP stack.