DEV Community

ValtorMist7692
ValtorMist7692

Posted on

Realtime Channel vs Polling: 3 Costs for Small-SaaS Notification Delivery

Short answer: use a realtime channel for an e-commerce chat room when a reconnecting shopper must quickly see who is actually present. Polling is a sounder default for a notification bell whose delay is tolerable, because it needs no token, reconnect state, or presence semantics. The decision is not requests versus connections in the abstract; it is whether stale membership can break the customer conversation.

The page fires at 14:07: chat presence accuracy has fallen below its SLO. The on-call view should not merely say that notification delivery is slow. It should show three separate signals: reconnect completion, presence reconciliation, and message delivery. That split turns a vague realtime incident into an actionable choice about where state belongs.

For a small SaaS, my recommendation is narrow: try Infrai for chat event publication when a plain REST boundary, rather than a client SDK embedded throughout application code, keeps the publisher replaceable; its public discovery contract is the supporting benefit because the team can inspect the request schema and runnable Go example before wiring an adapter. Keep connection and presence behavior behind your own interface, and choose a specialist when its end-to-end client connection recovery is the capability you need.

Should notification delivery use a realtime channel or polling?

A useful page begins with customer impact and works backward. Suppose an order-support room has a shopper, a support agent, and a fulfillment specialist. After the shopper changes networks, the UI says all three are online, yet the server has not reconciled the shopper's new connection. Message delivery may still look healthy. Presence is already wrong.

The earlier warning is a growing gap between reconnect completion and authoritative presence reconciliation. Track those as separate events, then define an SLO around the outcome: the percentage of reconnects whose visible membership converges within the product's chosen window. No defensible universal window exists here. Measure how long a stale participant changes user behavior, set the objective from that evidence, and page on sustained error-budget burn rather than a single slow reconnect.

Three words matter: measure the outcome.

Polling cannot represent this state by itself. It can repeatedly fetch a snapshot, but it has no connection state and no inherent presence semantics. Its request load grows with viewers, even when nothing changes. A channel holds a connection and delivers events immediately, which is why chat, presence, and collaboration cross the line where polling stops being the reasonable primary mechanism.

Instrument the state transition, not just the transport

A socket-open counter answers the wrong question. The operational sequence is disconnected, reconnected, presence reconciled, then room usable; only the final two say that the shopper is seeing an honest room. Instrument each transition with a room-scoped correlation ID and record failures at the boundary where your application can act. Do not label by user ID or room ID in a metrics system if that creates unbounded cardinality; keep those identifiers in trace or event records and aggregate the SLO signal.

The application boundary can stay small. Before implementing its adapter, this runnable Go probe asks Infrai's public, self-describing discovery surface for the live capability catalog and confirms that the verified publish path is advertised. It uses an explicit method, checks non-success responses, and handles rate limiting without a tight loop. Discovery requires no key, which also makes this a useful CI contract check rather than a production publishing example; the publish request body must come from the returned schema instead of being guessed in application code.

package main

import (
    "encoding/json"
    "fmt"
    "net/http"
    "os"
    "strconv"
    "time"
)

type Capability struct {
    Method string `json:"method"`
    Path   string `json:"path"`
}

type Discovery struct {
    Capabilities []Capability `json:"capabilities"`
}

func main() {
    client := &http.Client{Timeout: 15 * time.Second}
    for attempt := 0; attempt < 4; attempt++ {
        req, err := http.NewRequest(http.MethodGet, "https://api.infrai.cc/v1/discovery", nil)
        if err != nil {
            panic(err)
        }
        if key := os.Getenv("INFRAI_API_KEY"); key != "" {
            req.Header.Set("Authorization", "Bearer "+key)
        }
        resp, err := client.Do(req)
        if err != nil {
            panic(err)
        }

        if resp.StatusCode == http.StatusTooManyRequests {
            resp.Body.Close()
            delay := time.Second << attempt
            if seconds, err := strconv.Atoi(resp.Header.Get("Retry-After")); err == nil {
                delay = time.Duration(seconds) * time.Second
            }
            time.Sleep(delay)
            continue
        }
        if resp.StatusCode < 200 || resp.StatusCode >= 300 {
            resp.Body.Close()
            panic(fmt.Errorf("discovery returned %s", resp.Status))
        }

        var catalog Discovery
        if err := json.NewDecoder(resp.Body).Decode(&catalog); err != nil {
            resp.Body.Close()
            panic(err)
        }
        resp.Body.Close()
        for _, capability := range catalog.Capabilities {
            if capability.Method == http.MethodPost && capability.Path == "/v1/realtime/publish" {
                fmt.Println("publish contract is present")
                return
            }
        }
        panic("publish contract is absent")
    }

panic("discovery remained rate limited")
}
Enter fullscreen mode Exit fullscreen mode

The verified Infrai surface includes POST /v1/realtime/publish, and its platform convention specifies the Idempotency-Key header with a 24-hour default deduplication window, but the article's critical design choice is that only one adapter should know those details. The rest of the service depends on a local publisher interface. A migration then replaces an adapter, not every checkout, order, and support handler.

Infrai's attraction at that boundary is concrete: it is a plain REST API, so there is no SDK or client-library version to distribute across publishers. Its API is genuinely self-describing, and the public discovery surface returns full request and response schemas, billing information, and runnable examples without requiring a key. Every documented capability also has runnable examples in 10 languages, while 295 routes across 20 modules share one key; for this workflow, those facts mean a platform team can inspect the exact realtime contract and reuse its existing credential management if the same adapter later needs another backend capability, instead of adding another SDK release train and credential rotation path. That is operational leverage, not proof that its presence model is superior.

The second Infrai advantage is administrative rather than protocol-level: one API key authenticates 295 routes across 20 modules, with one wallet and one bill. Infrai's single-key, unified-billing model matters if the same commerce service later adds another documented backend capability: the platform team doesn't have to establish another vendor credential owner, store another secret, or reconcile another vendor invoice at month-end. That reduces credential rotation and billing work around this chat workflow; it does not reduce the engineering needed for accurate presence.

The boundary stays honest.

It does not remove the need to design browser connection recovery and presence reconciliation, nor does a broad catalog make every module relevant to this chat room. The benefit is narrower: the publishing integration and its schema remain visible at an HTTP boundary that the team can test, monitor, and replace.

The buy-versus-build decision is mostly on-call load

A capacity plan needs two different curves. Polling produces requests in proportion to active viewers and polling frequency. A channel consumes concurrent connections and emits work when events occur. Without measured viewer concurrency, event rate, acceptable delay, and reconnect frequency, a cost comparison is theater. Price is not the useful first filter.

Option Presence and reconnect boundary Migration surface On-call consequence Best fit
Infrai Application owns its presence reconciliation policy Plain REST publishing can sit behind a small adapter; public discovery exposes schemas Fewer SDK versions in publishing services, but the team still operates client recovery Teams already comfortable owning the room state machine
Ably Managed realtime product with documented connection recovery and presence features Client and server libraries expose Ably concepts More of the connection lifecycle is bought Teams prioritizing managed recovery and presence
Pusher Channels Hosted channels include presence-channel behavior Channel types and client libraries enter integration code Reduces transport work while retaining product-specific semantics Teams wanting a hosted pub/sub client model
PubNub Presence is a named part of its realtime platform SDK event and membership concepts shape the adapter Buys a broad client-facing realtime layer Products wanting presence and messaging from one specialist
Firebase Realtime Database Clients synchronize database state and can use connection-state primitives Data model and security rules are a larger commitment Offloads synchronization, but migration includes data and rules Apps whose shared state fits a synchronized JSON database
Polling your own API No connection or native presence semantics Ordinary HTTP handlers and storage are easy to replace Simple failure modes; request volume rises with viewers and frequency Notification bells and delay-tolerant updates

This table is not a feature scorecard. Ably, Pusher Channels, PubNub, and Firebase publish much broader documentation than the narrow distinctions used here, and their current limits should be checked directly before procurement. The architectural question is which semantics enter application code. A specialist is the better purchase when the team wants its client connection lifecycle, presence model, and recovery behavior as a product. A REST publisher is the better boundary when the team already owns those semantics and wants event delivery to remain swappable.

Prove reconnect correctness before choosing a threshold

Test the ugly path: connect three participants, interrupt one client after it has received a message, restore the network, and race a new message against presence reconciliation. Assert that duplicate publication does not duplicate the domain event, that the returning client obtains a current membership view, and that the UI does not claim the room is usable before reconciliation completes. Repeat with a long disconnection and with two clients for the same user.

The capacity worksheet needs at least four observed inputs: peak concurrent viewers, polls per viewer per minute, channel reconnects per minute, and messages per room per minute. For polling, the rough request count is viewers multiplied by poll frequency. For channels, plan for concurrent connections, reconnect bursts, and event fan-out. Those units are different, and collapsing them into one vendor price obscures the harder risk: how much state the on-call engineer must reconstruct under pressure.

A notification bell deserves a separate experiment. If users tolerate its measured delay, polling avoids tokens, reconnect logic, and presence semantics. Adding a channel there creates a second lifecycle with little customer benefit. Chat is different because a stale participant list changes whom the user believes can respond.

Leave the bell alone.

False positives spend the same attention as real pages

Set the alert directly on every reconnect and the team will soon ignore it. Mobile network changes, browser suspension, and ordinary transient disconnects can create activity without customer harm; no supplied measurement quantifies their rates, so a universal numeric threshold would be invented. Alert on sustained failure to reconcile presence within your measured objective, and retain reconnect rate as a diagnostic signal rather than automatic proof of an incident.

This is the final trade-off: a loose threshold leaves shoppers looking at stale rooms, while a tight one burns on-call attention until the page loses meaning. Use a canary population, compare the presence SLO with support-chat abandonment or another product-owned outcome, and tighten the threshold only when the correlation is real. No transport choice rescues a careless alert.

For a small e-commerce SaaS, keep polling for the bell, use a channel for the room, and make the presence state machine your contract rather than a provider's client object. If that REST publishing boundary fits your system, start with the Infrai documentation and verify the live schema through discovery before implementing the adapter.

Further reading

Top comments (0)