Skip to content
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

MSC2108: Sync over Server Sent Events #2108

Open
wants to merge 2 commits into
base: old_master
Choose a base branch
from
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 81 additions & 0 deletions proposals/2108-sync-via-server-sent-events.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
## Introduction

Currently, Matrix clients use long polling to get the latest state from the server,
it becomes an issue when you have a lot of clients because:
* homeserver needs to process a lot of requests, which by the way may return nothing
* it affects network bandwidth, each time client sends request it creates a new HTTP
request with headers, cookies and other stuff
* for mobile clients, it spends their cellular Internet and eats money
* when homeserver uses SSL over HTTP (what is recommended),
clients are doing again and again the most expensive operation, the TLS handshake

So, instead of long polling I propose to implement
sync logic over [Server Sent Events][mdn-sse](SSE)

## Proposal
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no mention of how limited syncs work in this proposal. Syncs can be limited due to a filter or due to the server's maximum willingness to serve events.

The existing sync endpoint is built for long polling, which doesn't really make it suitable for SSE. Although the sync format does lend itself to being a nice and backwards compatible data structure, I think it would be best if we used a more stream-oriented structure for SSE.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My thought on this was that it would work just like calling /sync repeatedly. For example, if you call /sync/sse with the Last-Event-ID set to some value, and the equivalent /sync call would have return a limited sync, then the first SSE event that you get will also be a limited result. Also, if the server is streaming SSE events, and you suddenly get a billion events in one room, then the next SSE event that it sends will be limited. It doesn't quite fit perfectly with a streaming model (for example, calling /sync/sse with the same Last-Event-ID two times won't give you the same result for the first event), but it fits with the way that Matrix currently handles truncating results. And I don't think SSE has a native method of skipping over events.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, adding some examples to the proposal may be useful.


[Server Sent Events][mdn-sse](SSE) is a way for servers to push events to clients.
It was a part of HTML5 standard and now available in all major web and mobile browsers.
It was specifically designed to overcome challenges related to short/long polling.
By introducing this technology, we can get the next benefits:
* only 1 persisted connection per client that is kept open "forever".
* SSE is built on top of HTTP protocol, so can be used in communication between servers
* SSE is more compliant with existing IT infrastructure like (Load Balancer, Firewall, etc)
uhoreg marked this conversation as resolved.
Show resolved Hide resolved
* web and mobile browsers support automatic reconnection and `Last-Event-Id` header out of the box
* Matrix protocol is built over HTTP, so SSE should fit good in protocol specification

Check this for details:
* https://medium.com/axiomzenteam/websockets-http-2-and-sse-5c24ae4d9d96
* https://www.html5rocks.com/en/tutorials/eventsource/basics/
* https://stackoverflow.com/questions/9397528/server-sent-events-vs-polling
* https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events
* supported browsers: https://caniuse.com/#feat=eventsource

SSE is easy to implement by your own on server side as it just a text based streaming on top of HTTP
but there are libraries which can do this for us (e.g., https://pypi.org/project/Flask-SSE/).
Support for SSE exists in Android and iOS libraries as well (few quickly googled):
* Android - http://kaazing.org/
* iOS - /~https://github.com/inaka/EventSource

This proposal doesn't change the shape of Matrix events or required parameters to do state synchronization
but instead propose to use a different underlying technology to do this.
As it was suggested in the related issue:
* lets expose `/sync/stream` URL for SSE in order to be backward compatible with other clients and servers
uhoreg marked this conversation as resolved.
Show resolved Hide resolved
* this URL returns the same data as continually calling `/sync`
* it accepts the same parameters as `/sync`, except `since` and `timeout`
Half-Shot marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how does set_presence work in this proposal?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question. My guess is that it would be equivalent to continually calling /sync with the same set_presence parameter, for as long as the SSE connection is alive. When the SSE connection dies, then the behaviour would be the same as a client no longer calling /sync. Maybe this should be made more explicit.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how does full_state work in this proposal?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question. I think that there are three options:

  1. drop full_state for SSE. If a client wants to do full_state, they can call /sync to start with, and then continue the sync using SSE.
  2. only return the full state for the first SSE event, and incremental state for subsequent SSE events
  3. return the full state for event SSE event. The first SSE event gets sent immediately (as it would when calling /sync with full_state), but subsequent SSE events only get sent when there's actually something new

Personally, I think my preference would be 2, then 1, then 3, with 1 and 2 being fairly close, and 3 being much less preferred.

* instead of using the `since` query parameter, the next batch token will be passed through the `Last-Event-ID` header.
Half-Shot marked this conversation as resolved.
Show resolved Hide resolved
* each event will have the same format as what `/sync` returns. The id of each event will be the `next_batch` token
uhoreg marked this conversation as resolved.
Show resolved Hide resolved
* the server sends events in exactly the same way that it would send responses to `/sync` calls with the `since`
parameter set to the previous `next_batch`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As per above, perhaps you could give a description on what an event is in SSE terms? I assume a SSE event is the body being sent along the wire, but it's not explicit here.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added links to documentation for the shape of matrix sync endpoint and for the format of SSE event.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It still doesn't make any sense to me: how is a client meant to resume their sync stream? All they'd have is a Last-Event-ID and a filter.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If their call to /sync/sse gets killed, then they just make a new call, with the Last-Event-ID header set to the last SSE event ID that it saw.

It may be helpful to give an example of how the requests/responses look, so that people don't have to go digging through the SSE docs.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 to examples. The SSE docs are a bit too complicated to try and parse in parallel here. Doesn't even need to be valid SSE:

GET /_matrix/client/r0/sync/sse
Last-Event-ID: 12

{"sync": "response_here"}


## Tradeoffs

Another alternative is to implement websocket communication for state synchronization. So, why not websockets?
* websockets are built over TCP protocol and requires implementation in server language
Half-Shot marked this conversation as resolved.
Show resolved Hide resolved
* some proxy servers or firewalls may block websockets
* HTTP/2 was standardized in 2015 without any mention of WebSockets
uhoreg marked this conversation as resolved.
Show resolved Hide resolved
and only in 2019 support for websockets over HTTP/2 was added.
* lack of built-in support for `Last-Event-Id` and automatic reconnection

The main difference between Websockets and SSE is that the first one is 2 way communication.
But with adoption of HTTP/2, which can be easily enabled in reverse proxy like nginx over HTTPS,
HTTP requests are cheaper than in HTTP/1.1 due to headers compression and request multiplexing.
So, it's not an issue anymore to send requests from client via regular HTTP and get events via SSE.

## Potential issues

Don't see issues. There are old clients which does not support SSE
uhoreg marked this conversation as resolved.
Show resolved Hide resolved
but polyfills with SSE implementation can be used.

## Security considerations

No security issues from SSE perspective

## Conclusion

By adopting SSE, homeservers will be able to keep higher load and bigger amount of clients
s SSE was designed to handle these cases with low consumption of resources.
SSE is just a HTTP streaming (basically `Content-Type`),
so it should fit good in existing HTTP infrastructure and Matrix protocol.

[mdn-sse]: https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events