|
| 1 | +# Lazy loading room membership over federation |
| 2 | + |
| 3 | +## Problem |
| 4 | + |
| 5 | +Joining remote rooms for the first time from your homeserver can be very slow. |
| 6 | +This is particularly painful for the first time user experience of a new |
| 7 | +homeserver owner. |
| 8 | + |
| 9 | +Causes include: |
| 10 | + * Room state can be big. For instance, a /send_join response for Matrix HQ is |
| 11 | + currently 24MB of JSON covering 28,188 events, and could easily take tens of |
| 12 | + seconds to calculate and send (especially on lower-end hardware). |
| 13 | + * All these events have to be verified by the receiving server. |
| 14 | + * Your server may have to fetch ths signing keys for all the servers who have |
| 15 | + sent state into the room. |
| 16 | + |
| 17 | +This also impacts peeking over federation |
| 18 | +([MSC2444](https://github.com/matrix-org/matrix-doc/pull/2444)), which is even |
| 19 | +more undesirable, given users expect peeking to have a very snappy UX, letting them |
| 20 | +quickly check links to sample rooms etc. |
| 21 | + |
| 22 | +For instance Gitter shows a usable peeked page for a room with 20K |
| 23 | +members in under 2 seconds (https://gitter.im/webpack/webpack) including |
| 24 | +launching the whole webapp. Similarly Discord loads usable state for a server |
| 25 | +with 90K users like https://chat.vuejs.org in around 2s. |
| 26 | + |
| 27 | +## Proposal |
| 28 | + |
| 29 | +The vast majority of state events in Matrix today are `m.room.member` events. |
| 30 | +For instance, 99.4% (30661 out of 30856) of Matrix HQ's state is |
| 31 | +`m.room.member`s (see Stats section below). |
| 32 | + |
| 33 | +Therefore, in the response to `/send_join` (or a MSC2444 `/peek`), we propose |
| 34 | +sending only the following `m.room.member` events if the initiating server |
| 35 | +includes `lazy_load_members: true` in their JSON request body. |
| 36 | + |
| 37 | + * the "hero" room members which are needed for clients to display |
| 38 | + a summary of the room (based on the |
| 39 | + [requirements of the CS API](https://github.com/matrix-org/matrix-doc/blob/1c7a6a9c7fa2b47877ce8790ea5e5c588df5fa90/api/client-server/sync.yaml#L148)) |
| 40 | + * any members which are in the auth chain for the state events in the response |
| 41 | + |
| 42 | +In addition, we extend the response to `/send_join` and `/peek` to include a |
| 43 | +`summary` block, matching that of the CS `/sync` API, giving the local server |
| 44 | +the necessary data to support MSC1227 CS API lazy loading. |
| 45 | + |
| 46 | +The joining server can then sync in the remaining membership events by calling |
| 47 | +`/state` as of the user's join event. To avoid retrieving duplicate data, we |
| 48 | +propose adding a parameter of `lazy_load_members_only: true` to the JSON |
| 49 | +request body which would then only return the missing `m.room.member` events. |
| 50 | + |
| 51 | +Clients which are not lazy loading members (by MSC1227) must block returning |
| 52 | +the CS API `/join` or `/peek` until this `/state` has completed and been |
| 53 | +processed. |
| 54 | + |
| 55 | +Clients which are lazy loading members however may return the initial `/join` |
| 56 | +or `/peek` before `/state` has completed. However, we need a way to tell |
| 57 | +clients once the server has finished synchronising its local state. For |
| 58 | +instance, clients must not let the user send E2EE messages until their server |
| 59 | +has acquired the full set of room members for the room, otherwise some of the |
| 60 | +users will not have the keys to decrypt the message. We do this by adding an |
| 61 | +`syncing: true` field to the room's `state` block in the `/sync` response. |
| 62 | +Once this field is missing or false, the client knows it is safe to call |
| 63 | +`/members` and get a full list of the room members in order to encrypt |
| 64 | +successfully. The field can also be used to advise the client to not |
| 65 | +prematurely call `/members` to show an incomplete membership list in its UI |
| 66 | +(but show a spinner or similar instead). |
| 67 | + |
| 68 | +While the joining server is busy syncing the remaining room members via |
| 69 | +`/state`, it will also need to sync new inbound events to the user (and old |
| 70 | +ones if the user calls `/messages`). If these events refer to members we're |
| 71 | +not yet aware of (e.g. they're sent by a user our server hasn't lazyloaded |
| 72 | +yet) we should separately retrieve their membership event so the server can |
| 73 | +include it in the `/sync` response to the client. To do this, we add fields |
| 74 | +to `/state` to let our server request a specific `type` and `state_key` from |
| 75 | +the target server. |
| 76 | + |
| 77 | +## Alternatives |
| 78 | + |
| 79 | +Rather than making this specific to membership events, we could lazy load all |
| 80 | +state by default. However, it's challenging to know which events the server |
| 81 | +(and clients) need up front in order to correctly handle the room - plus this |
| 82 | +list may well change over time. For instance, do we need to know the |
| 83 | +`uk.half-shot.bridge` event in the Stats section up front? |
| 84 | + |
| 85 | +Rather than reactively pulling in missing membership events as needed while |
| 86 | +the room is syncing in the background, we could require the server we're |
| 87 | +joining via to proactively push us member events it knows we don't know about |
| 88 | +yet, and save a roundtrip. This feels more fiddly though; we can optimise this |
| 89 | +edge case if it's actually needed. |
| 90 | + |
| 91 | +## Related |
| 92 | + |
| 93 | +MSC1228 (and future variants) also will help speed up joining rooms |
| 94 | +significantly, as you no longer have to query for server keys given the room |
| 95 | +ID becomes a server's public key. |
| 96 | + |
| 97 | +## Stats |
| 98 | + |
| 99 | +``` |
| 100 | +matrix=> select type, count(*) from matrix.state_events where room_id='!OGEhHVWSdvArJzumhm:matrix.org' group by type order by count(*) desc; |
| 101 | + type | count |
| 102 | +---------------------------+------- |
| 103 | + m.room.member | 30661 |
| 104 | + m.room.aliases | 141 |
| 105 | + m.room.server_acl | 23 |
| 106 | + m.room.join_rules | 9 |
| 107 | + m.room.guest_access | 6 |
| 108 | + m.room.power_levels | 5 |
| 109 | + m.room.history_visibility | 3 |
| 110 | + m.room.name | 1 |
| 111 | + m.room.related_groups | 1 |
| 112 | + m.room.avatar | 1 |
| 113 | + m.room.topic | 1 |
| 114 | + m.room.create | 1 |
| 115 | + uk.half-shot.bridge | 1 |
| 116 | + m.room.canonical_alias | 1 |
| 117 | + m.room.bot.options | 1 |
| 118 | +``` |
0 commit comments