DEV Community

ZebedeeHolloway9023
ZebedeeHolloway9023

Posted on

Postgres Event Replay Windows — Retention for Scaling Courier Location Maps

Short answer: use an application-owned Postgres checkpoint and an explicit expiry rule beside the realtime transport, so a delivery tracking map can recover by stable event ID without confusing connection presence with current courier state.

This ADR chooses presence accuracy over the comforting fiction of exactly-once network delivery. A delayed marker is visible; a marker that stays online after its lease expires, moves backward after a duplicate, or resumes from an unprovable position is much harder to reconcile. The transport moves events. The application defines what those events mean after reconnect.

How should realtime event retention policy scale a delivery tracking map?

The policy should preserve a stable reconciliation point, not every coordinate forever. For each device, the application owns an event ID, a monotonically increasing sequence, the time the device observed the location, the time the backend accepted it, and the expiry time of the derived presence state. These are fields in this design, not claims about a provider payload.

Four invariants govern the decision. Reapplying one event ID must leave the same materialized device state. An event with a lower sequence cannot replace one with a higher sequence, even when network delay reverses arrival order. Authentication, subscription state, and business events need separate audit records, because an open connection does not prove that a location update was authorized or committed. Finally, a reconnect begins reconciliation from the client's last stable identifier; it does not declare the local map current merely because the socket opened again.

Sequence wins.

This is an exactly-once mindset applied at the state transition rather than a promise about the network. Duplicate delivery is expected. If evt_018942 at sequence 7814 was committed before a mobile handoff, receiving it again may add a duplicate-observed audit fact, but it must not move the marker or extend its presence lease. A later arrival at sequence 7813 is evidence worth recording, yet it cannot overwrite sequence 7814.

Presence therefore needs more vocabulary than a Boolean. connected is transport evidence. online is an application judgment bounded by a lease or heartbeat expiry. stale means the last accepted business state can still be displayed with an age indicator but cannot be represented as current. offline means the lease expired or an authorized administrative disconnect was processed. Keep these states distinct — operators will otherwise read network state as business truth.

Invariants, expiry, and failure boundaries

Three clocks are involved. Device observation time explains the age of a GPS fix, backend acceptance time orders the auditable commit, and lease expiry governs presence. Device time cannot be the sole ordering authority because clocks can differ. Backend time alone also loses information when a device buffers an earlier observation and uploads it after connectivity returns. The stable sequence checkpoint joins those clocks without pretending either is sufficient.

A practical layout has a replay window for unapplied events, a current-state row per device, and a separate audit stream. There is no defensible universal duration in the available evidence. The maximum supported reconnect interval, operational investigation needs, access controls, deletion obligations, and the purpose for collecting courier location all affect the answer. Put each duration in versioned configuration, record the policy version beside the expiry decision, and test deletion as seriously as insertion. I'm not sure which duration is lawful for a particular deployment; the applicable contracts, jurisdiction, and a data-protection review must resolve that compliance limit.

Expiry is policy.

Partial failures are ordinary states. A business event may be authenticated while its subscriber no longer holds valid authorization. A dashboard may receive an update and lose connectivity before persisting its checkpoint. A publisher may miss an acknowledgement and retry. A subscription may reconnect after the device's presence lease has already elapsed. Model those boundaries independently, then test realistic latency, duplicate delivery, authorization denial, expiry, and recovery instead of merely opening two browser tabs and watching a dot move.

Keep recovery bounded as well. If a client's checkpoint predates the retained replay window, the correct response is a fresh authorized snapshot followed by new events, not an attempt to manufacture a continuous history. That transition belongs in the audit trail: which client requested recovery, which checkpoint it supplied, whether replay was available, which snapshot established the new baseline, and which policy version made the decision. It's tedious metadata until the first reconciliation dispute. Then it's the evidence.

Infrai exposes the verified administrative control POST /v1/realtime/user/disconnect. Its relevant advantage here is a plain, consistent REST contract whose provider can change behind the capability without forcing application code to change; one key and one bill span the broader backend surface, so this control does not require another vendor SDK, credential inventory, or reconciliation stream. Its public discovery surface also lets a deployment verify the selected capability's method and path before binding application code. The catch is important: a control-plane disconnect does not define retention, replay, or courier presence semantics. Postgres still owns the checkpoint, expiry decision, and audit record in this architecture.

Compare contracts before choosing a transport

A fair shortlist includes Ably, Pusher Channels, PubNub, and Infrai. The comparison cannot honestly be reduced to a feature-checkbox score because the supplied evidence does not establish equivalent plan limits, regional latency, or retention guarantees. Instead, use one acceptance suite and make each candidate prove the contract that matters to the map.

Option Contract to verify before selection When it can be the right choice
Ably Replay boundary, resume behavior, presence membership, and authorization expiry Choose it when the verified replay and presence contract covers the promised reconnect interval
Pusher Channels Presence departure timing, recovery behavior, duplicate handling, and channel authorization Choose it for an ephemeral dashboard when its verified channel semantics match the product and durable history remains application-owned
PubNub Configured retention, presence timeout, cursor behavior, and access control Choose it when its verified cursor and retention behavior passes the late-arrival and reconnect suite
Infrai Discovery schema, readiness, method and path, and the capability's idempotency declaration Choose it when a provider-neutral REST boundary and unified application contract outweigh a vendor-specific SDK experience

Run the same sequence against every candidate: accept an event, remove network access long enough to cross the configured presence lease, submit a duplicate and an older sequence during recovery, restore access, and compare the final marker, presence classification, checkpoint, and audit entries. Also test denied authorization separately from transport loss. Your mileage may vary by plan, configuration, and region, so preserve the results alongside the ADR rather than turning a current test outcome into a timeless vendor claim.

No latency, uptime, or savings claim follows from this comparison. Those claims require authenticated measurement in the target region under the expected connection count, including tail behavior. Don't infer them from a documentation page.

Critical path in Go and Postgres

Before binding the state machine to a transport control, this runnable Go preflight calls Infrai's self-describing discovery surface and verifies the method attached to the one route used by this ADR. Set INFRAI_BASE_URL to the documented API base and keep the key in INFRAI_API_KEY; the request uses an explicit method, checks the response body on failure, and honors Retry-After on HTTP 429.

package main

import (
    "encoding/json"
    "fmt"
    "io"
    "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() {
    baseURL := os.Getenv("INFRAI_BASE_URL")
    apiKey := os.Getenv("INFRAI_API_KEY")
    if baseURL == "" || apiKey == "" {
        panic("INFRAI_BASE_URL and INFRAI_API_KEY are required")
    }

    client := &http.Client{Timeout: 10 * time.Second}
    const wantedPath = "/v1/realtime/user/disconnect"

    for attempt := 0; attempt < 4; attempt++ {
        req, err := http.NewRequest(http.MethodGet, baseURL+"/discovery", nil)
        if err != nil {
            panic(err)
        }
        req.Header.Set("Authorization", "Bearer "+apiKey)

        resp, err := client.Do(req)
        if err != nil {
            panic(err)
        }
        if resp.StatusCode == http.StatusTooManyRequests {
            seconds, parseErr := strconv.Atoi(resp.Header.Get("Retry-After"))
            resp.Body.Close()
            if parseErr != nil || seconds < 1 {
                seconds = 1 << attempt
            }
            time.Sleep(time.Duration(seconds) * time.Second)
            continue
        }
        if resp.StatusCode < 200 || resp.StatusCode >= 300 {
            body, _ := io.ReadAll(resp.Body)
            resp.Body.Close()
            panic(fmt.Sprintf("discovery status %s: %s", resp.Status, body))
        }

        var document Discovery
        err = json.NewDecoder(resp.Body).Decode(&document)
        resp.Body.Close()
        if err != nil {
            panic(err)
        }
        for _, capability := range document.Capabilities {
            if capability.Path == wantedPath && capability.Method == http.MethodPost {
                fmt.Printf("verified %s %s\n", capability.Method, capability.Path)
                return
            }
        }
        panic("selected contract did not match discovery")
    }
    panic("discovery retry budget exhausted")
}
Enter fullscreen mode Exit fullscreen mode

The next program demonstrates the decisive state transition without inventing a transport payload. In production, Apply and the audit append belong in one Postgres transaction, with a unique constraint on event_id and serialization per device_id. The mutex makes this compact auxiliary example deterministic; the returned checkpoint is the value a reconnecting client can reconcile against.

package main

import (
    "fmt"
    "sync"
    "time"
)

type Event struct {
    EventID  string
    DeviceID string
    Sequence uint64
    Observed time.Time
}

type DeviceState struct {
    LastEventID string
    Sequence    uint64
    Observed    time.Time
    Accepted    time.Time
    Expires     time.Time
}

type Store struct {
    mu      sync.Mutex
    devices map[string]DeviceState
    seen    map[string]struct{}
    audit   []string
}

func NewStore() *Store {
    return &Store{
        devices: make(map[string]DeviceState),
        seen:    make(map[string]struct{}),
    }
}

func (s *Store) Apply(e Event, accepted time.Time, lease time.Duration) string {
    s.mu.Lock()
    defer s.mu.Unlock()

    if _, duplicate := s.seen[e.EventID]; duplicate {
        s.audit = append(s.audit, "duplicate:"+e.EventID)
        return "duplicate"
    }
    s.seen[e.EventID] = struct{}{}

    current, exists := s.devices[e.DeviceID]
    if exists && e.Sequence <= current.Sequence {
        s.audit = append(s.audit, "late:"+e.EventID)
        return "late"
    }

    s.devices[e.DeviceID] = DeviceState{
        LastEventID: e.EventID,
        Sequence:    e.Sequence,
        Observed:    e.Observed,
        Accepted:    accepted,
        Expires:     accepted.Add(lease),
    }
    s.audit = append(s.audit, "accepted:"+e.EventID)
    return "accepted"
}

func main() {
    store := NewStore()
    accepted := time.Date(2026, 9, 3, 10, 0, 0, 0, time.UTC)
    lease := 30 * time.Second

    events := []Event{
        {EventID: "evt_018942", DeviceID: "courier_204", Sequence: 7814, Observed: accepted.Add(-2 * time.Second)},
        {EventID: "evt_018942", DeviceID: "courier_204", Sequence: 7814, Observed: accepted.Add(-2 * time.Second)},
        {EventID: "evt_018941", DeviceID: "courier_204", Sequence: 7813, Observed: accepted.Add(-4 * time.Second)},
    }

    for _, event := range events {
        fmt.Printf("%s %s\n", event.EventID, store.Apply(event, accepted, lease))
    }
    state := store.devices["courier_204"]
    fmt.Printf("checkpoint=%s sequence=%d audit=%v\n", state.LastEventID, state.Sequence, store.audit)
}
Enter fullscreen mode Exit fullscreen mode

The 30 seconds above is example input, not a recommendation. A real service reads the approved lease from versioned configuration. It must also refuse an event before this transition when authorization fails, while recording authentication and subscription decisions separately from the business-event audit. For a retrying write to any external API, retain the same client-supplied idempotency identity; for HTTP 429, honor Retry-After when present and otherwise use bounded exponential backoff. Never let a retry mint a new logical event.

This core also clarifies what the realtime layer cannot repair. Once replay has expired, it cannot infer an omitted coordinate from the current marker. The snapshot path must establish a new checkpoint explicitly, and the UI must represent stale or offline state while that happens. Quietly extending a lease from an old snapshot would make the map look healthy while weakening its evidence chain.

Rejected option and its valid use case

The rejected design is transport-owned state: subscribe on page load, treat channel membership as courier presence, keep the last event only in browser memory, and assume reconnect resumes without a gap. It is attractive because there is little backend code. It is not suitable when a delivery tracking map must explain duplicates, late events, authorization expiry, or why a courier changed from online to stale. A browser refresh destroys the only checkpoint, and transport presence answers a different question from whether the last authorized business update remains current.

Stick with that simpler design for a disposable demonstration where no operational decision depends on the marker, no durable recovery is promised, and stale presence has no consequence. Pusher Channels or another SDK-centered option may offer the more convenient developer experience there. For an operational map, however, retain application-owned checkpoints and audit facts even if the selected transport offers replay.

The decision rule is narrow: select a provider only after its verified reconnect and authorization behavior passes the same suite, but keep correctness portable in Postgres. That leaves vendor choice reversible while the retention policy, stable identifiers, and presence semantics remain under application control.

References

Top comments (0)