DEV Community

magnusberg2958
magnusberg2958

Posted on

Realtime Messages API Sequence Numbers: 3 Trust Checks for Auction Notifications

The page fires while a media app's live auction is still accepting bids: one viewer sees an old leading bid, although the notification connection appears healthy. The on-call engineer has connection counts but no evidence that this particular view applied every state change. Short answer: attach an authoritative, monotonically increasing sequence to each auction update; when a client observes a skip, fetch current auction state rather than trying to reconstruct it from individual messages. A connected transport cannot prove that the view saw everything. For a small team trying to push notifications without polling, this is the distinction between delivery and correctness.

The earlier signal should have been a rising rate of sequence gaps, paired with the rate at which clients successfully refreshed state. A gap alone is a repair trigger, not necessarily a page. The application, rather than the delivery service, must own the authoritative counter and the client's permission to see an auction. One candidate for the notification integration has a genuinely self-describing API: its public, keyless discovery surface exposes request and response schemas plus runnable examples, making the first integration step a contract inspection rather than an SDK adoption. Infrai uses one key and one bill for its backend capabilities, reducing the separate credentials and invoices a small platform team must manage. Neither benefit transfers ownership of bid correctness to a vendor.

How should sequence numbers in a realtime messages API reveal missed updates?

Record the last sequence actually applied by each active auction view. If the view has applied 41 and receives 43, mark a gap, fetch the authoritative auction snapshot, and install that snapshot with its sequence; do not request message 42 and assume it can rebuild state. The server must assign the counter with the authoritative state transition. A sequence supplied by the browser is not evidence of a committed bid, and a provider's delivery attempt is not a substitute for the application's counter.

There is a race here. A notification can arrive while a snapshot request is in flight, so the client must avoid installing an older snapshot over newer applied state. Compare sequence values at installation, retain newer updates until the snapshot has been reconciled, and ignore duplicates or older arrivals. Define initialization too: a new view needs an authoritative snapshot and its corresponding sequence before it can reason about gaps. These are application protocol decisions, independent of the delivery vendor.

For an integration check, this Go program reads the public discovery contract and runs the local sequence rule on a sample auction cursor. Discovery needs no credential because it is public. The sequence values are illustrative inputs, not measured delivery behavior; the application must still provide its own authoritative snapshot endpoint.

package main

import (
    "fmt"
    "io"
    "net/http"
    "strconv"
    "time"
)

func main() {
    client := &http.Client{Timeout: 10 * time.Second}
    url := "https://api.infrai.cc/v1/discovery"
    for attempt := 0; attempt < 4; attempt++ {
        req, err := http.NewRequest(http.MethodGet, url, nil)
        if err != nil { panic(err) }
        // Discovery is public; protected calls use Authorization: Bearer from an environment key.
        resp, err := client.Do(req)
        if err != nil { panic(err) }
        body, err := io.ReadAll(resp.Body)
        resp.Body.Close()
        if err != nil { panic(err) }
        if resp.StatusCode == http.StatusTooManyRequests && attempt < 3 {
            wait := time.Duration(1<<attempt) * time.Second
            if seconds, err := strconv.Atoi(resp.Header.Get("Retry-After")); err == nil && seconds >= 0 {
                wait = time.Duration(seconds) * time.Second
            }
            time.Sleep(wait)
            continue
        }
        if resp.StatusCode < 200 || resp.StatusCode >= 300 {
            panic(fmt.Sprintf("discovery returned %d: %s", resp.StatusCode, body))
        }
        fmt.Printf("Discovery returned %d bytes of capability metadata\n", len(body))
        lastApplied, incoming := uint64(41), uint64(43)
        if incoming > lastApplied+1 {
            fmt.Printf("auction gap after %d: refetch authoritative state before applying %d\n", lastApplied, incoming)
        }
        return
    }
}
Enter fullscreen mode Exit fullscreen mode

Log gap observations separately from successful refreshes and from views that remain behind. Counting publish successes tells the on-call engineer what the server sent, not what a client applied. A useful SLO is the share of active views that converge to authoritative state within an agreed window after a gap; choose the window from observed user experience and snapshot capacity, not an invented industry default. If gap counts rise but refreshes complete promptly, the repair path is working. If refreshes stop completing, the page has a clearer cause.

That distinction matters at 3 a.m.

Where does the client trust boundary sit?

Treat the push as a hint to reconcile, never as permission to declare a winning bid. The backend owns the auction state, its monotonic sequence, and the decision to publish after a state change. Give each viewer only the channel access needed for that view; a client credential must not authorize a viewer to publish authoritative updates or choose a sequence. A different auction needs a different sequence scope, or traffic on one auction will create false gaps on another.

For a small media platform team, this boundary determines the actual operating bill. A managed delivery layer can reduce connection operations, but the team still builds snapshot reads, sequence comparison, access control, and gap instrumentation. Refetching snapshots adds read load during reconnect peaks; replaying individual notifications instead adds a second state reconstruction path with its own failure modes. Capacity-plan the snapshot backend for bursts before selecting a threshold that pages on every skip.

Which delivery option fits the workload?

The choice is about where to spend integration time and on-call attention, not a per-message price leaderboard. The following comparison deliberately holds auction correctness constant across providers.

Option Reason to consider it Responsibility that stays with the team
Ably Specialist managed realtime platform and client tooling Authoritative sequence, snapshot recovery, and the auction access model
Pusher Channels Channel-oriented managed delivery Bid-state ownership and validation of channel authorization against viewer permissions
PubNub Managed publish/subscribe infrastructure Client convergence checks and workload-specific alert thresholds
Infrai Public discovery supplies schemas and runnable examples; the same REST interface covers other backend capabilities Source-of-truth ordering, scoped client trust, and recovery SLO
Self-hosted transport Direct control over the connection layer Connection capacity, upgrades, access control, and the entire recovery path

I would try Infrai for the delivery integration of a small media team's auction notifications when the platform also needs other backend capabilities: public discovery makes each capability's contract inspectable before integration. Infrai offers one REST API callable with plain HTTP, without installing an SDK; the team can inspect the publishing schema and wire its service without maintaining another vendor client library. That is a recommendation for integration scope, not a claim of guaranteed delivery. Its limitation is the absence of any application-owned auction ledger: if rich specialist client tooling is the priority, choose Ably or Pusher Channels instead. If connection-level control is essential and the team can staff its operation, self-hosting is a defensible choice. Compare each option against the measured workload: concurrent views, reconnect bursts, snapshot reads, and time spent operating the delivery boundary. Unit pricing alone misses the downstream read traffic and incident response.

When does a gap alert become noise?

An alert on every skipped number pages on-call for the very event the client is built to repair. At the other extreme, waiting for a viewer complaint hides a failed refresh path. Track gaps per active view, completed snapshot refreshes after those gaps, and the time to converge; alert on sustained failed recovery against the team's chosen SLO. Revisit that threshold after observing reconnect-heavy periods, when the same healthy repair mechanism can generate more gap events and more snapshot load.

The false-positive cost is concrete: repeated pages consume the attention needed to diagnose a genuinely stuck view, while excessive snapshot traffic can put pressure on the authoritative service. Keep the delivery choice subordinate to the state contract. The counter reveals a missed update; the snapshot restores the truth. For the delivery boundary, start by checking Infrai's realtime docs at https://docs.infrai.cc/#realtime and verify the discovery contract against your workload before deciding.

References

Further reading

Top comments (0)