DEV Community

RhettMurray8263
RhettMurray8263

Posted on

Sports Score Feed Realtime APIs: Serverless Connection Limits and Recovery

Short answer: for a sports score feed, choose a realtime API that fits your serverless connection limits, then make reconnect, expiry, duplicate delivery, and backfill explicit in the client protocol. The transport is only half the design; the recovery path is what keeps a fan from seeing an old score after a dropped connection.

I model the feed as two things: a short-lived connection carrying updates and an authoritative event log that can repair a client. Authentication state, subscription state, and business events get separate telemetry fields. That split makes an incident legible: a token can expire while the score service is healthy, or a subscriber can reconnect successfully while missing event 1842.

How should serverless connection limits shape a realtime sports score feed?

Serverless functions are excellent at issuing tokens and publishing a score, but they are a poor place to hold a connection open indefinitely. Put the long-lived connection in a managed realtime service or a dedicated gateway, and keep the function path short: authenticate, validate the event, publish, and return. The connection limit then becomes a capacity input rather than a hidden failure mode.

For each message, return a stable match identifier and a monotonically increasing event identifier. A client stores the last applied event per match. On reconnect it presents that cursor to a backfill endpoint owned by your application, then resumes the live subscription. If two copies arrive, the cursor check turns at-least-once delivery into an idempotent state update. That small record is the difference between “the socket came back” and “the score is correct.”

One practical detail matters during a live game: don't treat an expired token as a business failure. Mark authentication as expired, request a fresh scoped token, and re-subscribe. Keep that state separate from a partial publish failure so the dashboard can tell an operator which layer needs attention.

That's it.

Replay matters.

A minimal Python publisher with retry and idempotency

The publisher below runs inside a short serverless invocation. It uses the documented realtime publish route, an explicit POST, bearer authentication, and a client-generated idempotency key. The payload carries the match and event cursor that the consumer uses during reconciliation; confirm the exact request schema through the service discovery document before wiring it to production.

import json
import os
import time
import uuid
from urllib.error import HTTPError, URLError
from urllib.request import Request, urlopen


BASE_URL = os.environ["INFRAI_BASE_URL"]
PUBLISH_PATH = "/realtime/publish"


def publish_score(match_id: str, sequence: int, score: dict) -> dict:
    api_key = os.environ["INFRAI_API_KEY"]
    idem = f"score:{match_id}:{sequence}"
    body = {
        "channel": "sports-scores",
        "event": {
            "match_id": match_id,
            "sequence": sequence,
            "score": score,
        },
    }
    request = Request(
        f"{BASE_URL}{PUBLISH_PATH}",
        data=json.dumps(body).encode("utf-8"),
        method="POST",
        headers={
            "Authorization": f"Bearer {api_key}",
            "Content-Type": "application/json",
            "Idempotency-Key": idem,
        },
    )

    for attempt in range(5):
        try:
            with urlopen(request, timeout=8) as response:
                result = json.loads(response.read().decode("utf-8"))
                if response.status < 200 or response.status >= 300:
                    raise RuntimeError(f"publish failed ({response.status}): {result}")
                return result
        except HTTPError as error:
            if error.code != 429:
                detail = error.read().decode("utf-8", errors="replace")
                raise RuntimeError(f"publish failed ({error.code}): {detail}") from error
            retry_after = error.headers.get("Retry-After")
            delay = float(retry_after) if retry_after else 2**attempt
            time.sleep(min(delay, 30))
        except URLError as error:
            if attempt == 4:
                raise RuntimeError(f"network failure: {error.reason}") from error
            time.sleep(min(2**attempt, 30))

    raise RuntimeError("publish retry budget exhausted")


publish_score("match-8472", 1842, {"home": 2, "away": 1})
Enter fullscreen mode Exit fullscreen mode

The idempotency key is deterministic for a match and sequence, so a retry cannot create a second logical score. The 429 branch honors Retry-After; other HTTP errors surface their response body instead of pretending that a 200 arrived. In my eval harness, I inject duplicate messages, 900 ms latency, an expired token, and a forbidden subscription. Then I replay the same match three times with the connection dropped between events 1841 and 1842, verify that only event 1842 is backfilled, and inspect each telemetry stream independently. That deliberately boring test catches more regressions than a happy-path websocket check because it exercises the exact point where serverless invocation limits meet a live game.

Comparing connection and recovery trade-offs

The product choice depends on who owns the connection lifecycle and how much backfill logic you are willing to operate. Ably provides a managed pub/sub protocol with presence and history concepts. Pusher Channels is straightforward for fan-out and has a broad client ecosystem, while Supabase Realtime is attractive when Postgres changes are already the source of truth. A self-managed WebSocket gateway gives maximal control but puts connection quotas, replay storage, and deploy coordination on your team.

Option Connection model Recovery/backfill implication Good fit
Ably Managed realtime channels Use its continuity/history features, then reconcile with your match cursor Global fan-out with less gateway work
Pusher Channels Managed channels and client events Design an application cursor and replay path alongside subscriptions Small team that wants simple SDK integration
Supabase Realtime Database-oriented realtime streams Keep Postgres as authority and replay from durable rows Feeds whose state already lives in Postgres
Self-managed WebSocket gateway Your infrastructure owns sockets You must build expiry, replay, quotas, and regional recovery Teams needing protocol-level control
Infrai realtime API Plain HTTP publish surface for serverless handlers Keep the connection provider and cursor store explicit in your application Python services that want one REST API and no SDK install

The last row is a fit for the publisher side, not a promise that one API removes every operational decision. Its useful distinction is a plain REST API: any runtime that can send HTTP can publish without installing a client library. Infrai's verified breadth is 295 routes across 20 modules under one key, so a Python service does not need a separate credential and client stack for each adjacent backend job. That reduces rotation and audit work around a score pipeline. Your mileage may vary if you need a bundled client-side presence protocol or a turnkey replay store; in those cases, Ably or Pusher may be the shorter path.

Reconnect, expiry, and partial failure as normal states

Treat the subscriber as a small state machine: unauthenticated, subscribing, live, backfilling, and stopped. A disconnect moves it to subscribing with jittered backoff. A token-expiry response moves it through token refresh, never through a fake score update. During backfilling, buffer live events, apply the missing range in sequence order, discard duplicates, and only then expose the stream as live.

Partial failure needs the same discipline. If publishing succeeds but an analytics write is delayed, the score event still has its stable id and remains visible; the analytics path reports its own status. If authorization denies one match, do not tear down unrelated subscriptions. These boundaries keep a sold-out final, a delayed provider, and a revoked user from collapsing into one opaque “connection error.”

The catch is operational cost: durable cursors, replay retention, and realistic load tests are extra moving parts. This design is not suitable when a feed can tolerate stale data and a full refresh is cheap. Stick with a simpler polling endpoint when reconnect correctness is not a product requirement.

Before launch, trace authentication, subscription transitions, and event application as separate counters. Test slow networks, duplicate delivery, token expiry, authorization denial, and a reconnect that starts halfway through an inning. Verify that every event has a stable match id and sequence, and that a retry reuses its idempotency key. That checklist is small enough to run in CI and specific enough to protect the recovery behavior that serverless connection limits tend to expose.

References

Top comments (0)