Hacker News new | past | comments | ask | show | jobs | submit login
Deprecate GraphQL Subscriptions over WebSockets in Favour of SSE/EventSource API
24 points by jensneuse 32 days ago | hide | past | favorite | 14 comments
In this post, I'll discuss the pros and cons of using WebSockets to implement GraphQL Subscriptions. I'll then propose why we should consider deprecating the WS implementation in favour of a much simpler to use API, namely EventSource. Would love to hear your opinion!

https://wundergraph.com/blog/deprecate_graphql_subscriptions_over_websockets




Based on my experience, SSEs add more complexity and brittleness to applications compared to WebSockets. Having a single WebSocket connection to handle both RPCs and subscriptions is great because it binds all data flows to the lifecycle of a single socket - So if that single connection fails, it's easy to identify the disconnection and recover from it.

Another major benefit of WebSockets is that you can control the exact timing of RPCs and subscriptions without having to worry about race conditions when the server expects to receive actions in a specific order.

Also, SSEs add a lot of overhead because you need to authenticate each SSE channel independently (unlike with WebSockets where you only need authenticate a socket once at the beginning and then use it to subscribe to almost unlimited channels); so if you have many SSE EventSources, establishing them will waste a lot of resources since you need to pass the auth token (or session ID) to each EventSource that you open - In general, SSE authentication is tricky and forces you to rely on cookies which can be a problem in a lot of situations (e.g. on mobile if the front end is loaded the local file system as part of a WebView, your cookies will not be sent to the server due to cross-origin restrictions).

Moreover, the lack of control over the lifecycle of the SSE connection makes it difficult to coordinate recovery from network or server failure/restart; a common problem happens when your server crashes and then all the SSE event sources try to reconnect immediately at the same time and this DDoSes your server again, then the reboot and crash cycle repeats indefinitely... With WebSockets, you can control the reconnect algorithm to add exponential backoff, for example (it can be customized to your exact requirements).

Finally, the statefulness of WebSocket connections can be a huge advantage; you can store data pertaining to a single active client in-memory on the server-side which can be convenient for a lot of use cases (and efficient). For example, you can attach an auth token on the back end socket (in memory) and can use it to quickly check access rights for any channel without having to make an additional database call.

SSEs are not a practical abstraction IMO. It's sad that people don't realize how good the WebSocket standard is. It's good because it's simple; it's also what makes it so flexible. WebSockets cover more use cases.

Subscriptions as a concept have to support too many use cases (too generic) to be implemented with something as restrictive as SSEs.


Websocket is definitely a superior abstraction, but the main problem with it is that a lot of corporate firewalls/network proxies don't know how to handle the upgrade request and drop the connection. We took a dependency on Websockets a few years back and while everything worked fine in the US, a lot of our customers in Asia/Europe simply couldn't access some of the core functionality of our site.

The Server-sent-events/EventSource API _seems_ like a better fit for GraphQL subscriptions because it's one-directional traffic anyway, but in addition to the issues you mentioned above, you'll also run into the 2000-character limit most browsers have for URLs [1]. The browser's EventSource constructor does not let you specify request bodies so you're forced to put everything in the URL, which is _not_ suited for the graphql queries which tend to get pretty large. I ended up writing an alternate EventSource parser using the fetch() api [2] to get around this limitation.

Happily, there seems to be two alternate approaches in the pipeline that seem more robust/better supported:

1. The proposed @defer graphql directive [3] recommends using multi-part HTTP as the standard transport layer, so hopefully all the tooling like GraphQL Playground will support a single-request-multiple-response scenario over plain HTTP

2. For scenarios where we need interactive, bidirectional communication, the proposed WebTransport API [4] seems to solve some of the issues with websockets.

[1] https://stackoverflow.com/a/417184

[2] https://github.com/Azure/fetch-event-source

[3] https://www.apollographql.com/docs/react/v2.4/features/defer...

[4] https://github.com/WICG/web-transport


You don't have to put the Query into the URL when using Persisted Queries.

The link about the @defer implementation looks very interesting. Thanks for posting this.


> Another major benefit of WebSockets is that you can control the exact timing of RPCs and subscriptions without having to worry about race conditions when the server expects to receive actions in a specific order.

This is not related to GraphQL.

> Also, SSEs add a lot of overhead because you need to authenticate each SSE channel independently (unlike with WebSockets where you only need authenticate a socket once at the beginning)

I wouldn't consider this a lot of overhead. You're re-using an open TCP connection. You have to send a token which needs to be validated, sure. That's a sub millisecond overhead.

> In general, SSE authentication is tricky and forces you to rely on cookies

You can send a token as query parameter. Using TLS this is no different than a Header.

> Moreover, the lack of control over the lifecycle of the SSE connection makes it difficult to coordinate recovery from network or server failure/restart; a common problem happens when your server crashes and then all the SSE event sources try to reconnect immediately at the same time and this DDoSes your server again, then the reboot and crash cycle repeats indefinitely... With WebSockets, you can control the reconnect algorithm to add exponential backoff (can be customized to your exact requirements).

The EventSource API allows you to close the connection at any time. It will automatically reconnect which I consider a feature. There's also a "onopen" and "onerror" callback. You can use these to control reconnect behaviour and implement an exponential backoff strategy.

I don't see the difference between WebSockets and SSE in case of a server Crash. Both will reconnect and DDos the server.

> SSEs are not a practical abstraction IMO. It's sad that people don't realize how good the WebSocket standard is. It's good because it's simple; it's also why it's so flexible. WebSockets covers more use cases.

You're absolutely right. However, we don't need this flexibility for GraphQL Subscriptions.

I feel like what you're saying makes sense in general. However, in the use case of GraphQL Subscriptions I don't see advantages using WebSockets over SSE.


>> I wouldn't consider this a lot of overhead.

Depends on how many subscriptions you want. If you have 100s of subscriptions, it can add up.

>> You can send a token as query parameter.

If using JWT or some other kind of stateless signed (or encrypted) token, that would take up a large part of the URL. Also URL length is limited to around 2000 characters in some browsers. So it encourages short session IDs instead of signed tokens (session IDs are not stateless; so it's going to force an additional database call to be made on the server-side).

>> The EventSource API allows you to close the connection at any time. It will automatically reconnect which I consider a feature.

Yes there is some control. But if it auto-reconnects too fast, then your servers could end up DDoSed if you had a lot of concurrent clients. If it auto-reconnects too slow, then your users could miss more messages than would be desirable depending on the use case.

With WebSockets, you can control the backoff to your needs and you can randomize, so you can ensure that not all clients try to reconnect at the exact same time; you can tailor it to your architecture.

>> However, we don't need this flexibility for GraphQL Subscriptions.

It seems like it simplifies things but also potentially reduces flexibility and performance. But I'm not familiar with the typical use case for GraphQL so maybe it's worth it.


I have to agree - with GraphQL subscriptions you don’t need full duplex. You are just subscribing.


To the OP - you are not supposed to include any text in a submission with a link. The text box is really only for 'Ask HN' and things like that.


You're right. Maybe a mod can change this? Sorry!


> WebSockets are an outdated technology deemed dead thanks to the ietf not adding HTTP/2 support

This is the first sentence, and I’m dubious - not about the lack of HTTP/2 but that WebSockets are dead, because I’ve never heard this until now. Is this really the case?


If your website is HTTP/2 all the way, do you really want to add WebSockets which force the browser to initiate another TCP connection? The EventSource or Streams API can easily replace WebSockets for most use cases.


On the Web nothing really dies. However, WebSockets are from the HTTP/1 era. They were PITA to support even in HTTP/1 servers, and they're even more annoying in HTTP/2 deployments.


There's this: https://github.com/CodeCommission/subscriptions-transport-ss...

Although it doesn't seem to be maintained anymore.


Really nice


please post this as a link post if you're sharing a link!




Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact

Search: