How does realtime work via HTTP with Redis?

HEAD
faa20d0chore: version package

how does the real time work via http with redis?

Avatar of openai/gpt-5.6-sol
GPT 5.6 Sol
commented

It combines Redis Streams for durability with Redis Pub/Sub for immediate delivery, then exposes events to browsers through HTTP Server-Sent Events (SSE).

Publisher
   │  XADD + PUBLISH
   ▼
Upstash Redis
   │  Pub/Sub subscription
   ▼
Your /api/realtime endpoint
   │  HTTP SSE stream
   ▼
Browser EventSource

###1. Publishing an event

Calling:

await realtime.channel("chat").emit("message.created", data)

does two things in src/server/realtime.ts:

  1. XADD stores the event in a Redis Stream and generates an ID such as 1712345678901-0.
  2. PUBLISH sends that same event to active subscribers immediately.

Conceptually:

const id = await redis.xadd(channel, "*", {
  data,
  event,
  channel,
})

await redis.publish(channel, {
  id,
  data,
  event,
  channel,
})

Redis Pub/Sub provides low-latency notification, while the Stream provides history and recovery if a client disconnects.

###2. Browser connection over HTTP

The React provider creates a normal browser EventSource:

new EventSource(
  "/api/realtime?channel=chat&last_ack_chat=1712345678901-0"
)

The API handler returns a streaming HTTP response with:

Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive

Each Redis message is encoded as an SSE frame:

data: {"id":"1712345678901-0","channel":"chat","event":"message.created","data":{...}}

This is implemented in src/server/handler.ts. There is no application WebSocket connection—the browser keeps an HTTP response open and the server continuously writes SSE messages into it.

###3. Reconnection and missed events

The client remembers the last Redis Stream ID received for each channel. On reconnect it sends that ID as last_ack_<channel>.

The server then executes an exclusive stream range:

redis.xrange(channel, `(${lastAck}`, "+")

That replays events newer than the acknowledged ID before normal live delivery continues.

To avoid a race while history is loading, the server:

  1. Starts Redis Pub/Sub first.
  2. Buffers newly arriving live events.
  3. Loads missing events from the Redis Stream.
  4. Sends the history.
  5. Deduplicates buffered events using their stream IDs.
  6. Flushes the remaining buffer and switches to live delivery.

Thus, Pub/Sub handles “right now”, while Redis Streams handle “what did I miss?”

###4. Connection maintenance

The implementation also:

  • Publishes a ping every 60 seconds.
  • Treats a missing ping after 75 seconds as a dead connection.
  • Sends a reconnect system event shortly before the serverless function’s maximum duration.
  • Reopens the SSE request and resumes from the latest acknowledged stream ID.

So “real time via HTTP” here means a long-lived streaming HTTP response, not HTTP polling:

Redis Stream = persistence/replay
Redis Pub/Sub = immediate notification
SSE/EventSource = HTTP delivery to the browser

END OF POST

How does realtime work via HTTP with Redis? — upstash/realtime