New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Consider SSE to enable server->client http subscriptions #544
Comments
SSE should be possible to add alongside WebSockets. I prefer WS myself as I don't have to think about max concurrent connection limit and my application doesn't have user numbers that make it hard to scale anyway. The way our HTTP subscriptions work right now is through long polling and you pass a cursor of the current state and server let's the request hang until there's new data / times out. Inspect https://chat.trpc.io/ to see how the long polling works. |
I too love websockets, when they work. At scale have had issues with OOM memory causing a host of issues. The major issues at scale have been token based access timers, sticky load balancing, if a server goes down it takes everyone with it. I'll dig into the examples more but this was just to plant the idea in your head. 😃 |
Isn't scaling with SSE similar though, or am I missing something? It's still a persistent connection between a client and server(s) that also needs to be scaled. |
Scaling is not my area of expertise, but with WS and the current setup it should scale seamlessly on things like k8s/render.com - we'll listen to a
|
process.on('SIGTERM', () => { | |
console.log('SIGTERM'); | |
handler.broadcastReconnectNotification(); | |
wss.close(); | |
}); |
ReconnectError
trpc/packages/server/test/websockets.test.ts
Lines 439 to 484 in dcfff9e
test('ability to do do overlapping connects', async () => { | |
const { client, close, wsClient, ee, wssHandler, wss } = factory(); | |
ee.once('subscription:created', () => { | |
setImmediate(() => { | |
ee.emit('server:msg', { | |
id: '1', | |
}); | |
}); | |
}); | |
function createSub() { | |
const onNext = jest.fn(); | |
const onError = jest.fn(); | |
const onDone = jest.fn(); | |
const unsub = client.$subscription('onMessage', undefined, { | |
onNext, | |
onError, | |
onDone, | |
}); | |
return { onNext, onDone, onError, unsub }; | |
} | |
const sub1 = createSub(); | |
await waitFor(() => { | |
expect(sub1.onNext).toHaveBeenCalledTimes(2); | |
}); | |
wssHandler.broadcastReconnectNotification(); | |
await waitFor(() => { | |
expect(sub1.onError.mock.calls[0][0].originalError).toBeInstanceOf( | |
ReconnectError, | |
); | |
expect(wss.clients.size).toBe(2); | |
}); | |
const sub2 = createSub(); | |
await waitFor(() => { | |
expect(sub2.onNext).toHaveBeenCalledWith({ | |
type: 'started', | |
}); | |
}); | |
sub1.unsub(); | |
await waitFor(() => { | |
expect(wss.clients.size).toBe(1); | |
}); | |
wsClient.close(); | |
close(); | |
}); |
If you are handling reconnect that's great, the SSE spec has it built in. Since you are relying on |
I'll close for now, will do more testing with the websocket approach until I have better reason to seek alternatives. |
graphql-yoga 2 added subscriptions exclusively through SSE, since HTTP/2 you don't have to care about max concurrent connections as it was in the past |
I'll reopen this and see if someone wants to work on it for the next major :) |
Im writing map app for one game, where data for map tiles is send by HTTP request, and i want to update the client map in real time, |
Very similar situation to @neronim1141 Had everything migrated and then realized SSE is not working. |
It would be really awesome to have SSE. It is awesome for situations where you want to give some partial responses without setting a full and expensive WS. Really wanting this to be a thing |
I believe that a lot of cases don't really need the bidirectional communication nowadays - SSE is a perfect fit and plays well with HTTP/2 - A lot of the graphql community is also leaning toward this approach of HTTP/2 queries/mutations combined with SSE. A good read on the matter: https://wundergraph.com/blog/deprecate_graphql_subscriptions_over_websockets Would really like to see this worked on in the context of tRPC 🙏 |
Before going with SSE I suggest reading https://dev.to/miketalbot/server-sent-events-are-still-not-production-ready-after-a-decade-a-lesson-for-me-a-warning-for-you-2gie. It describes pitfall that cannot be solved on code level but instead on infrastructure one - to which we (devs) don't always have full access to. |
It can be solved on your side, you close connections going to proxy servers that doesn't support HTTP/2 or higher. |
Huh? Then the TRPC client is never going to receive subscription events? |
No, you close after the event is sent |
Look at this: https://mercure.rocks/ |
Good comparison of polling vs SSE vs web sockets ( at scale ) |
One huge advantage about using SSE over WS (assuming you only need server->client events, not bidirectional) is that you don’t need to spin up an additional server. This is a big deal in terms of infra complexity. And you’re not using a different protocol. |
@KATT Hello, first of all, thanks for this awesome package !! I'll be happy to try implementing SSE in TRPC, do you have some insight or starting point in order to implement such feature ?? Anyway, would be happy to help 😃 |
I know @carere volunteered to tackle this but we couldn't wait at my company. So I went ahead and implemented this. I'll be submitting a P.R here shortly. |
@leonwilly looking forward to it! |
@leonwilly Should we expect that anytime soon? No pressure just tryna get an ETA :) |
Hopefully end of this sprint (Monday) I'm swamped right now. |
@leonwilly when you have time it would be amazing to get that pr, it would be great for the trpc community. |
I'd like to help test the SSE functionality, any progress so far? |
Hey all! I was in a similar situation as leonwilly and implemented this for work. Bare-boned and extracted its available at https://github.com/OutdatedVersion/trpc-sse-link. recording.mp4Since I've been telling myself I'd figure out the best way to get this into mainline tRPC ... for a month please anyone feel free to take any of that code for a PR. ❤️ |
Nice work @OutdatedVersion this is good stuff! I've been waiting/wanting SSE in tRPC for a while and this is an amazing start, however, I'm not sure a stream for every subscription like this is the best approach. Instead, I would expect a shared stream that has an accompanied endpoint to post sub/unsub topics is a better approach. This accompanied endpoint would just be handled by useSubscription or whatnot still. Similar to how Twitter web client connects to https://api.twitter.com/live_pipeline/events which starts an eventstream with an optional There are many reasons why a single stream is best here but one main reason is the 100 connections per domain default limit with HTTP/2 and 6 connections on HTTP/1.1. If you have multiple things that need events and user has more than 1 tab open you'll hit the limit pretty quickly with multiple streams per event. |
@OutdatedVersion did you manage to make it work on Router handler (Next 13 app -> API). the event's arent sent and request finishes immediately here is my attempt:
|
@OutdatedVersion unsubscription doesn't actually stop the Observable from running, does it? |
For people who may be interested I managed to make a production-ready version of SSE using app dir route handlers. |
Will SSE continue to run on the back end when the user actively shuts it down? |
@StringKe to my understanding yes, as I know so far AbortSignal is bugged/doesn't exist on the next 13.4.4 app route API handlers, I also wrote it down on one of the comments in the code. |
Hey @matannahmani , I was using your repo implementation of tRPC + SSE as reference for a tRPC + SSE + svelte version, but since a few hours ago the repo is gone and all the links are broken, is there any way so we still have access for it as a reference? Thanks. |
@marschr for sure, here is a new repo dedicated to SSE-Link, I will try to make some work during this weekend to showcase ways you can use it and ways we are using it (LangChain integration). on a side note I will try to make a pull request to trpc main repo with The link and docs so people can view in the future for reference instead of having to search the issues every time, I see the progress on the SSELink is halted #4477 |
Reading through the #532 PR it occurred to me that the JSON-RPC notification system only works one way, from client to server. Most of the time you want it to be from server to client. Although nothing about the spec says which side is which, in a browser this basically means the browser is the client.
One interesting approach could be to use Server Sent Events. This would allow for stateless load balance-able subscriptions that could work with HTTP2. From what I'm seeing though I'm not sure if it would technically fall under a JSON-RPC complaint spec; though you probably can treat as a server to client notification. Nice thing is this is already built into browsers with polyfills available for node and most languages.
I think you'd create/cancel as you do currently but the client would also fetch on a new route /subscription/3nxu4ex234. Know this is separate from the websocket implementation but SSE isn't a well known. The major downside of the SSE spec as it stands is the text only protocol (compared to binary for websockets) but in a JSON-RPC context the point is moot.
TRP-63
Funding
The text was updated successfully, but these errors were encountered: