You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
SSE stream sends : connected before it is registered for live events, so early events are lost #784
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):
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.
Creates the subscriber (stream.NewSubscriber).
May write and flush the schema frame (h.Hub.SubscribeSchemaFrame).
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.
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
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.onopenfires, 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 soonopen, should only be sent once the stream is registered for live events. A client can then rely on one simple rule: every event ingested afteronopenfires is delivered on this stream.How it happens
The stream handler in
internal/api/stream.godoes its setup in this order (line numbers frommain):: connectedand flushes it. The client sees the response start, andonopenfires. The comment says this is soonopenfires right away, instead of waiting for the first data event.stream.NewSubscriber).h.Hub.SubscribeSchemaFrame).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 arrivesThe 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
Last-Event-ID. A first connection has no cursor, so nothing replays the event.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 withDeliverNewPolicy, 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
liveQueryREST backfill happens to cover the window. A just-ingested row may not be queryable yet.