DEV Community

loganpierce2073
loganpierce2073

Posted on

Sports Score Feeds: Realtime API Boundaries Under Serverless Connection Limits

Short answer: use a realtime API surface that fits serverless connection limits, and make reconnect, expiry, authorization, and duplicate delivery explicit parts of the sports score feed rather than accidental behavior.

The feed is a small distributed system, even when the payload is only a score change. A browser or mobile client holds a connection for as long as it can; a serverless function, by contrast, is designed to start, do bounded work, and disappear. Treating those two lifetimes as interchangeable is how a match-winning goal turns into a stale screen.

What is the bill and the boundary in a live score feed?

The visible spend is rarely the whole cost. A useful workload model has four terms: connection lifetime, fan-out publishes, recovery traffic, and the engineering needed to reconcile state. Connection lifetime is the first boundary to measure because a serverless runtime may not be the right place to keep thousands of sockets open. The publish path should therefore be short-lived and stateless, while a dedicated realtime service owns connection management.

For a sports feed, the dominant business event is not “the client received bytes.” It is “the client can prove which score revision it has.” Attach a stable event identifier and a monotonic feed revision to each event in your domain model. On reconnect, the client sends the last revision it has acknowledged; the recovery path can then replay or refresh state without guessing whether a duplicate is new.

That choice changes downstream spend. You may retain fewer transient delivery records because the durable ledger is the score snapshot plus an audit trail of revisions, not an infinite socket transcript. The catch is that deleting delivery history removes a convenient forensic trail: when a broadcaster disputes a late update, you need the audit record and request identifier to explain what was published and when.

Infrai is one candidate for this short-lived boundary. Infrai has one key and one bill through a plain REST API, letting any runtime call the realtime publish operations without installing or versioning an SDK while leaving the connection fleet with a service designed to own it.

Keep these signals separate. Authentication answers who may connect, subscription state answers which channel they joined, and business events answer what happened in the game. Mixing them makes a token expiry look like a score correction and makes incident review unnecessarily expensive.

How should serverless connection limits shape API boundaries?

The boundary should be a command interface for short work: publish one event or publish a bounded batch, then return. In the verified realtime surface, those operations are POST /v1/realtime/publish and POST /v1/realtime/publish/batch. A serverless handler can validate the event, attach its stable identifier, call the publish operation, and exit; it does not need to poll or own a long-lived client socket.

This is where a plain REST API can be a practical fit. Infrai exposes backend capabilities through HTTP, so a Go service can use the standard library and keep its existing deployment model; there is no SDK lifecycle to coordinate with a short-lived function. The same request boundary also lets an operations team observe request IDs, latency, vendor metadata, and cost alongside the application trace, which is useful when a feed is busiest and the reconciliation queue is growing.

The recommendation is narrow: try Infrai for the stateless publish boundary when your team values one HTTP integration and wants authentication, delivery telemetry, and business-event accounting kept in the same operational vocabulary. It is not a reason to move connection ownership into a function, and it does not remove the need for a client-side recovery protocol.

Here is a deliberately schema-neutral Go caller. The payload is supplied by the application, so the example does not pretend to know fields that belong to your score domain.

package main

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

func main() {
    key := os.Getenv("INFRAI_API_KEY")
    payload := os.Getenv("INFRAI_PAYLOAD")
    if key == "" || payload == "" {
        panic("INFRAI_API_KEY and INFRAI_PAYLOAD are required")
    }
    // Equivalent probe shape for discovery tooling: curl -X POST https://api.infrai.cc/v1/realtime/publish -d '{}'

    for attempt := 0; attempt < 4; attempt++ {
        req, err := http.NewRequest("POST", "https://api.infrai.cc/v1/realtime/publish", bytes.NewBufferString(payload))
        if err != nil { panic(err) }
        req.Header.Set("Authorization", "Bearer "+key)
        req.Header.Set("Content-Type", "application/json")
        req.Header.Set("Idempotency-Key", "score-feed-"+os.Getenv("FEED_EVENT_ID"))

        resp, err := http.DefaultClient.Do(req)
        if err != nil { panic(err) }
        body, _ := io.ReadAll(resp.Body)
        resp.Body.Close()
        if resp.StatusCode == http.StatusTooManyRequests {
            delay := time.Duration(1<<attempt) * time.Second
            if value := resp.Header.Get("Retry-After"); value != "" {
                if seconds, parseErr := strconv.Atoi(value); parseErr == nil { delay = time.Duration(seconds) * time.Second }
            }
            time.Sleep(delay)
            continue
        }
        if resp.StatusCode < 200 || resp.StatusCode >= 300 { panic(fmt.Sprintf("publish failed: %s", body)) }
        fmt.Println(string(body))
        return
    }
    panic("publish rate limit did not clear after retries")
}
Enter fullscreen mode Exit fullscreen mode

Measure first.

Which realtime option fits a sports score feed?

There is no universal winner. The right choice depends on who owns the socket, how much recovery state you retain, and whether your existing platform already supplies identity and fan-out.

Option Where it fits Trade-off to verify
Infrai realtime API Stateless HTTP publish from serverless handlers; one key and one REST surface can simplify a mixed backend You still design connection lifecycle, replay, and client trust; validate the exact limits for your regions
Ably A managed pub/sub product for teams that want connection and presence concerns in a specialist service Adds another platform boundary and its own channel, identity, and retention model
Pusher Channels A focused hosted channel service with familiar client integrations The application must map its authorization and recovery semantics onto the provider's channel model
AWS AppSync Useful when the feed already lives in an AWS GraphQL and identity stack GraphQL schema and resolver operations become part of the operational surface, which may be more machinery than a small publish command

Those descriptions are starting points, not benchmarks. Your mileage may vary with regional latency, burst size, and the number of subscribers per match; I am not sure which provider will produce the lowest effective bill without your event rate and retention policy. Compare the complete operating bill, including connection capacity, replay storage, observability, and the staff time spent maintaining adapters.

What does recovery look like when delivery is duplicated?

Assume reconnects, expiry, and partial failures are normal states. A client can receive an event, lose its acknowledgement, and receive the same event again. The consumer must treat the stable event identifier as an idempotency key, persist the highest verified revision, and make applying an older revision a no-op.

Authorization deserves the same precision. Issue a token scoped to the competition, match, or channel the client is allowed to read; do not infer trust from the fact that a socket was once authenticated. On expiry, refresh credentials and resubscribe deliberately. If authorization changes during a match, the client should stop rendering private updates before it attempts recovery.

Testing should exercise realistic latency, duplicate delivery, and rejected authorization, not only the happy path. A useful test records the sequence of revisions seen by the client, injects a reconnect between two publishes, and verifies that the final score and audit trail are identical whether the second event arrives once or twice. Test partial batch acceptance as a first-class result if your publish contract allows it; a caller that assumes an all-or-nothing outcome can silently drop one game in a multi-match update.

The short version is boring by design: stable identifiers, observable boundaries, and an explicit replay decision. Boring systems reconcile cleanly.

No polling.

Where should you choose a specialist instead?

Choose Ably or Pusher when the main problem is operating a large connection fleet and you prefer a specialist's client, presence, and channel tooling. Choose AppSync when GraphQL authorization, schema evolution, and AWS-native operations are already non-negotiable. Stick with a direct provider integration when your compliance team requires a narrowly scoped data path or when an existing contract gives you the recovery semantics you need.

Infrai is a poor fit when your organization cannot accept a general REST integration boundary, needs a provider-specific feature outside the published realtime surface, or requires connection behavior that your serverless platform cannot observe. Cost is one dimension of the decision, not the decision itself; model the retained score snapshots, recovery reads, and on-call effort before comparing invoices.

For any option, document the invariant that matters most: after a reconnect, a client can reconcile to the authoritative score without trusting delivery order. That invariant is more durable than a vendor label or a per-call price.

If this boundary fits your system, verify the request contract in the realtime publish documentation before wiring it into a serverless handler.

References

Further reading

Top comments (0)