Skip to content

SSE stream sends : connected before it is registered for live events, so early events are lost #784

Description

@EricAndrechek

What is wrong

When a client opens GET /v1/stream, WaveHouse tells it the stream is connected before the stream is listening for live events.

For a short window after the client's EventSource.onopen fires, an event ingested for that table goes to every other open stream but not to this one. It is never sent to this stream later, and the client gets no error or signal that it missed anything.

So a client cannot treat "connected" as "subscribed". Code that opens a stream and then acts, for example a UI that opens a live view and then submits a row it expects to see, can wait forever for an event that was already published.

Expected behaviour

: connected, and so onopen, should only be sent once the stream is registered for live events. A client can then rely on one simple rule: every event ingested after onopen fires is delivered on this stream.

How it happens

The stream handler in internal/api/stream.go does its setup in this order (line numbers from main):

  1. Line 91: writes : connected and flushes it. The client sees the response start, and onopen fires. The comment says this is so onopen fires right away, instead of waiting for the first data event.
  2. Creates the subscriber (stream.NewSubscriber).
  3. May write and flush the schema frame (h.Hub.SubscribeSchemaFrame).
  4. Line 125: registers with the hub (h.Hub.Add(topic, role, sub)). The hub is what fans each ingested event out to the open streams. A stream receives live events only from this point on.

Between step 1 and step 4, the client believes it is subscribed, but the hub does not know the stream exists:

sequenceDiagram
    participant C as Client
    participant H as Stream handler
    participant Hub as Hub
    participant I as Ingest
    C->>H: GET /v1/stream
    H-->>C: ": connected" (step 1, line 91)
    Note over C: onopen fires, client believes it is subscribed
    H->>H: create subscriber, write schema frame (steps 2 and 3)
    I->>Hub: event E ingested for this table
    Hub->>Hub: fan E out to the streams already registered, not this one
    H->>Hub: Hub.Add (step 4, line 125)
    Note over H,Hub: this stream receives live events only from here on
    Note over C: waits for E, which never arrives
Loading

The window is usually very short, but it widens when the server is busy, because the handler's goroutine runs later after each step.

Why the missed event is never recovered

  • Replay only runs for a client that resumes with Last-Event-ID. A first connection has no cursor, so nothing replays the event.
  • A later reconnect does not recover it either. The cursor is the last event the client received. If that was a later event F, replay starts after F, and E came before F.

A related, narrower window at process start

With external NATS, each process's hub bridge (ExternalNATS.Subscribe, internal/mq/external.go:711) reads the history stream with DeliverNewPolicy, from the moment its consumer is created. An event stored before that is never fanned out on that process. This only affects a process that has just started. Whether readiness (/readyz) waits for the bridge to exist has not been checked.

Where it showed up

#768's cross-process NATS e2e test opened a stream on each of two processes and ingested as soon as both reported open. On CI, process B's stream timed out waiting for the event (mq_nats_test.go:214), while process A's stream had already received it. That fits B being "connected" but not yet registered. This is inferred: the failure did not reproduce locally. The #768 fix works around the gap by ingesting probe rows until both streams have received one. Once this issue is fixed, those probes can go, and the test can assert the rule above directly.

Not checked

  • Whether the SDK's liveQuery REST backfill happens to cover the window. A just-ingested row may not be queryable yet.
  • What the streaming docs currently promise about delivery after connect.

Activity

  1. changed the title [-]SSE stream reports connected before it is registered for live events[/-] [+]SSE stream sends `: connected` before it is registered for live events, so early events are lost[/+] on Oct 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions