Short answer: choose managed realtime channels over a self-hosted broker for a 2026 delivery tracking map when presence accuracy and explicit recovery matter more than owning every transport detail; keep retention, reconnect, and reconciliation as application invariants either way.
The map is not a chat demo. A courier marker that disappears for 20 seconds can send a dispatcher to the wrong address, while a duplicated “arrived” event can trigger a second notification or a bad ledger entry. I would therefore separate three observability streams: authentication, subscription state, and business events. They have different owners, retention periods, and incident signals.
Presence is a claim with an expiry, not a boolean stored forever. The client should know whether it is seeing a fresh heartbeat, a replay after reconnect, or a deliberate disconnect. That distinction is the foundation of an exactly-once mindset at the business boundary, even though the network itself generally delivers at least once.
How should a delivery tracking map handle realtime event retention and recovery?
Write the decision record around failure boundaries. Authentication answers “may this device subscribe?” Subscription state answers “is the connection currently live?” Business events answer “what did the courier report, and in what order?” A retention policy that mixes those records makes recovery opaque: a valid token can look like a live driver, and a stale map update can overwrite a newer stop status.
Use stable identifiers on every business event: delivery_id, event_id, and a monotonic event_version allocated by the delivery service. On reconnect, the client sends its last applied version per delivery; the recovery service returns a snapshot plus events after that version, or marks the delivery for a fresh snapshot when the retention window has expired. The UI may show “location age” while this happens. It must not silently convert missing history into “driver offline.”
The retention table should be explicit and boring:
| Record | Purpose | Suggested boundary | Recovery action |
|---|---|---|---|
| Auth decision | Prove subscription is allowed | Short, security-owned retention | Re-authorize before resubscribe |
| Subscription state | Explain connect, expiry, and disconnect | Keep enough history for incident review | Rebuild from current connection state |
| Delivery event | Reconcile map and status | Keep through the business replay window | Replay by event_version; snapshot after expiry |
A reconnect is normal state. So are token expiry and a partial publish failure. Emit separate correlation identifiers for those transitions, then join them in dashboards rather than parsing one overloaded log line. In a payment or ledger backend, I would also retain an audit record of the state transition and actor; the map can be eventually consistent, but the delivery history must remain explainable.
How do managed channels compare with self-hosted brokers for event retention?
Managed channels remove a class of operational work: connection fan-out, regional routing, and provider failover sit behind a service contract. A self-hosted broker gives deeper control over partitioning, retention storage, and wire behavior, but your team owns capacity planning, upgrades, reconnect storms, and the pager when presence drifts. Neither option supplies a correct business history automatically.
Here is the comparison I would put in an architecture review. Product capabilities change, so verify limits and retention terms against each vendor's current documentation before committing.
| Option | Strength for a live map | Cost in correctness work | Choose it when |
|---|---|---|---|
| Ably | Managed presence and channel history | Vendor-specific semantics still need an adapter | You need managed global fan-out and can accept a hosted contract |
| Pusher Channels | Quick pub/sub and presence primitives | Recovery and durable event history remain application responsibilities | The map is modest and delivery replay is implemented elsewhere |
| Socket.IO | Familiar client protocol and self-managed deployment | You operate scale, backpressure, and retention components | You need protocol control and have an on-call platform team |
| Infrai realtime surface | A plain REST contract can sit behind a provider swap, so client code keeps its event contract while the backend capability changes | You still design retention, authorization, and reconciliation rules | You want one HTTP integration across backend capabilities and will own the domain policy |
The useful Infrai angle here is portability of the contract, not a price claim: one REST API and one key can front multiple backend capabilities, so changing the service behind a capability does not force a rewrite of the delivery event schema. Infrai has one key and one bill for its backend capabilities, and its surface covers 295 routes across 20 modules, which avoids a separate credential for map events, audit storage, and notification work. That is valuable when the map shares workflows with other services. Its broad surface is also self-describing through a public discovery document, which reduces the friction of checking request and response shapes before wiring a new recovery path. It does not absolve the team from defining an expiry or proving which event won a race.
A minimal disconnect path with observable retries
The only route I use in this small example is the verified user disconnect operation. It is an administrative transition, so the caller records the reason and correlation id in its own audit stream; the API call itself should be treated as a state change whose response status is checked.
package main
import (
"context"
"fmt"
"io"
"net/http"
"os"
"strconv"
"time"
)
func disconnect(ctx context.Context, userID string) error {
key := os.Getenv("INFRAI_API_KEY")
if key == "" {
return fmt.Errorf("INFRAI_API_KEY is required")
}
baseURL := os.Getenv("INFRAI_BASE_URL")
if baseURL == "" {
return fmt.Errorf("INFRAI_BASE_URL is required")
}
url := baseURL + "/v1/realtime/user/disconnect"
for attempt := 0; attempt < 4; attempt++ {
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, nil)
if err != nil {
return err
}
req.Header.Set("Authorization", "Bearer "+key)
req.Header.Set("X-Delivery-User", userID)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
body, readErr := io.ReadAll(io.LimitReader(resp.Body, 4096))
resp.Body.Close()
if readErr != nil {
return readErr
}
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
return nil
}
if resp.StatusCode != http.StatusTooManyRequests || attempt == 3 {
return fmt.Errorf("disconnect failed: status=%d body=%s", resp.StatusCode, string(body))
}
delay := time.Duration(1<<attempt) * 250 * time.Millisecond
if retryAfter := resp.Header.Get("Retry-After"); retryAfter != "" {
if seconds, parseErr := strconv.Atoi(retryAfter); parseErr == nil {
delay = time.Duration(seconds) * time.Second
}
}
select {
case <-ctx.Done():
return ctx.Err()
case <-time.After(delay):
}
}
return fmt.Errorf("disconnect retry budget exhausted")
}
The example deliberately does not pretend that a disconnect request is the event ledger. The delivery service must write its own idempotent transition keyed by userID and correlation id, then publish the resulting state. If a retry races with a reconnect, the state machine decides which transition is valid; transport order is not a business rule.
Keep this boundary visible.
What I would test before choosing a retention window
Load tests should include realistic latency, duplicate delivery, and authorization changes. Inject a 300 ms connection delay, duplicate an “arrived” event, expire a token mid-subscription, and drop only one publish in a batch. Assert that the client converges to one state identified by event_version, that unauthorized subscriptions are rejected, and that a replay after expiry requests a snapshot instead of guessing.
I started with the assumption that a longer retention window would make recovery safer. It does help operators inspect history, but it also increases storage and replay volume, and it can hide a client that never checkpoints. In a delivery map, imagine a courier crossing a tunnel while three location updates queue, one status update is duplicated, and the token expires before the radio reconnects; the recovery code must first re-authorize, then apply the snapshot, then discard any event whose version is older than the stored state, while the audit trail records why the marker was temporarily stale. Your mileage may vary: choose the shortest window that covers the map's reconnect objective plus an operator investigation margin, then measure snapshot frequency and reconciliation lag.
Small windows can be correct.
The catch is that a managed channel is not suitable when regulatory rules require the transport and raw event log to remain inside infrastructure your team controls, or when you need broker-level partition tuning that the service does not expose. Stick with Socket.IO or another self-hosted design in that case, and budget for durable storage, capacity tests, and a real on-call rotation. Conversely, a self-hosted broker is a poor fit for a small team whose primary risk is an inaccurate presence signal during a regional traffic spike.
The decision is conditional, but it is not vague: pick managed channels for operational reach, keep recovery explicit, and reject any design that cannot explain a marker's age, authorization state, and event version after reconnect.
Top comments (0)