-
Notifications
You must be signed in to change notification settings - Fork 384
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
MSC2775: Lazy loading over federation
A first cut at an MSC to define how to massively speed up joins (and MSC #2444 peeks) by sending irrelevant events after you join rather than up front
- Loading branch information
Showing
1 changed file
with
118 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
# Lazy loading room membership over federation | ||
|
||
## Problem | ||
|
||
Joining remote rooms for the first time from your homeserver can be very slow. | ||
This is particularly painful for the first time user experience of a new | ||
homeserver owner. | ||
|
||
Causes include: | ||
* Room state can be big. For instance, a /send_join response for Matrix HQ is | ||
currently 24MB of JSON covering 28,188 events, and could easily take tens of | ||
seconds to calculate and send (especially on lower-end hardware). | ||
* All these events have to be verified by the receiving server. | ||
* Your server may have to fetch ths signing keys for all the servers who have | ||
sent state into the room. | ||
|
||
This also impacts peeking over federation | ||
([MSC2444](https://github.com/matrix-org/matrix-doc/pull/2444)), which is even | ||
more undesirable, given users expect peeking to have a very snappy UX, letting them | ||
quickly check links to sample rooms etc. | ||
|
||
For instance Gitter shows a usable peeked page for a room with 20K | ||
members in under 2 seconds (https://gitter.im/webpack/webpack) including | ||
launching the whole webapp. Similarly Discord loads usable state for a server | ||
with 90K users like https://chat.vuejs.org in around 2s. | ||
|
||
## Proposal | ||
|
||
The vast majority of state events in Matrix today are `m.room.member` events. | ||
For instance, 99.4% (30661 out of 30856) of Matrix HQ's state is | ||
`m.room.member`s (see Stats section below). | ||
|
||
Therefore, in the response to `/send_join` (or a MSC2444 `/peek`), we propose | ||
sending only the following `m.room.member` events if the initiating server | ||
includes `lazy_load_members: true` in their JSON request body. | ||
|
||
* the "hero" room members which are needed for clients to display | ||
a summary of the room (based on the | ||
[requirements of the CS API](https://github.com/matrix-org/matrix-doc/blob/1c7a6a9c7fa2b47877ce8790ea5e5c588df5fa90/api/client-server/sync.yaml#L148)) | ||
* any members which are in the auth chain for the state events in the response | ||
|
||
In addition, we extend the response to `/send_join` and `/peek` to include a | ||
`summary` block, matching that of the CS `/sync` API, giving the local server | ||
the necessary data to support MSC1227 CS API lazy loading. | ||
|
||
The joining server can then sync in the remaining membership events by calling | ||
`/state` as of the user's join event. To avoid retrieving duplicate data, we | ||
propose adding a parameter of `lazy_load_members_only: true` to the JSON | ||
request body which would then only return the missing `m.room.member` events. | ||
|
||
Clients which are not lazy loading members (by MSC1227) must block returning | ||
the CS API `/join` or `/peek` until this `/state` has completed and been | ||
processed. | ||
|
||
Clients which are lazy loading members however may return the initial `/join` | ||
or `/peek` before `/state` has completed. However, we need a way to tell | ||
clients once the server has finished synchronising its local state. For | ||
instance, clients must not let the user send E2EE messages until their server | ||
has acquired the full set of room members for the room, otherwise some of the | ||
users will not have the keys to decrypt the message. We do this by adding an | ||
`syncing: true` field to the room's `state` block in the `/sync` response. | ||
Once this field is missing or false, the client knows it is safe to call | ||
`/members` and get a full list of the room members in order to encrypt | ||
successfully. The field can also be used to advise the client to not | ||
prematurely call `/members` to show an incomplete membership list in its UI | ||
(but show a spinner or similar instead). | ||
|
||
While the joining server is busy syncing the remaining room members via | ||
`/state`, it will also need to sync new inbound events to the user (and old | ||
ones if the user calls `/messages`). If these events refer to members we're | ||
not yet aware of (e.g. they're sent by a user our server hasn't lazyloaded | ||
yet) we should separately retrieve their membership event so the server can | ||
include it in the `/sync` response to the client. To do this, we add fields | ||
to `/state` to let our server request a specific `type` and `state_key` from | ||
the target server. | ||
|
||
## Alternatives | ||
|
||
Rather than making this specific to membership events, we could lazy load all | ||
state by default. However, it's challenging to know which events the server | ||
(and clients) need up front in order to correctly handle the room - plus this | ||
list may well change over time. For instance, do we need to know the | ||
`uk.half-shot.bridge` event in the Stats section up front? | ||
|
||
Rather than reactively pulling in missing membership events as needed while | ||
the room is syncing in the background, we could require the server we're | ||
joining via to proactively push us member events it knows we don't know about | ||
yet, and save a roundtrip. This feels more fiddly though; we can optimise this | ||
edge case if it's actually needed. | ||
|
||
## Related | ||
|
||
MSC1228 (and future variants) also will help speed up joining rooms | ||
significantly, as you no longer have to query for server keys given the room | ||
ID becomes a server's public key. | ||
|
||
## Stats | ||
|
||
``` | ||
matrix=> select type, count(*) from matrix.state_events where room_id='!OGEhHVWSdvArJzumhm:matrix.org' group by type order by count(*) desc; | ||
type | count | ||
---------------------------+------- | ||
m.room.member | 30661 | ||
m.room.aliases | 141 | ||
m.room.server_acl | 23 | ||
m.room.join_rules | 9 | ||
m.room.guest_access | 6 | ||
m.room.power_levels | 5 | ||
m.room.history_visibility | 3 | ||
m.room.name | 1 | ||
m.room.related_groups | 1 | ||
m.room.avatar | 1 | ||
m.room.topic | 1 | ||
m.room.create | 1 | ||
uk.half-shot.bridge | 1 | ||
m.room.canonical_alias | 1 | ||
m.room.bot.options | 1 | ||
``` |