Introduce support for MSC4429: Profile Updates for Legacy Sync#19556
Introduce support for MSC4429: Profile Updates for Legacy Sync#19556anoadragon453 wants to merge 175 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
Implements experimental MSC4429 support by introducing a new profile_updates stream (persisted + replicated) and surfacing profile field changes in legacy /sync when enabled.
Changes:
- Add
profile_updatesstream tracking (DB schema, stream token field, notifier + replication stream plumbing). - Emit MSC4429 profile updates in
/syncresponses and add filter support for selecting profile fields. - Record profile field updates on profile mutations (set/unset displayname/avatar/custom fields, deactivation cleanup).
Reviewed changes
Copilot reviewed 21 out of 21 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/rest/client/test_rooms.py | Updates hardcoded stream tokens to match the new StreamToken shape. |
| tests/rest/admin/test_room.py | Updates hardcoded stream tokens to match the new StreamToken shape. |
| synapse/types/init.py | Adds PROFILE_UPDATES stream key and extends StreamToken serialization/parsing. |
| synapse/streams/events.py | Includes profile_updates_key in current/bounded stream tokens. |
| synapse/storage/schema/main/delta/94/01_profile_updates_seq.sql.postgres | Adds Postgres sequence for the new stream. |
| synapse/storage/schema/main/delta/94/01_profile_updates.sql | Adds profile_updates table + indexes. |
| synapse/storage/schema/init.py | Bumps schema version to 94 and documents the change. |
| synapse/storage/databases/main/profile.py | Adds profile update persistence/query APIs and stream ID generator wiring. |
| synapse/rest/client/versions.py | Advertises org.matrix.msc4429 in unstable_features. |
| synapse/rest/client/sync.py | Encodes MSC4429 profile update payload into /sync response. |
| synapse/replication/tcp/streams/_base.py | Adds ProfileUpdatesStream replication stream. |
| synapse/replication/tcp/streams/init.py | Registers ProfileUpdatesStream for replication. |
| synapse/replication/tcp/handler.py | Replicates ProfileUpdatesStream from configured writers. |
| synapse/replication/tcp/client.py | Handles incoming profile update replication and notifies listeners. |
| synapse/notifier.py | Allows StreamKeyType.PROFILE_UPDATES to wake /sync waiters. |
| synapse/handlers/sync.py | Generates initial + incremental MSC4429 profile update sections in /sync. |
| synapse/handlers/profile.py | Records profile update markers on profile mutations and notifies. |
| synapse/config/experimental.py | Adds msc4429_enabled config flag. |
| synapse/api/filtering.py | Adds /sync filter support for selecting profile fields (MSC4429-gated). |
| docker/complement/conf/workers-shared-extra.yaml.j2 | Enables MSC4429 in complement worker test config. |
| changelog.d/19556.feature | Adds changelog entry for MSC4429 experimental support. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
83e699f to
a846945
Compare
dc8e9f8 to
a309372
Compare
We create a stream table dedicated to tracking profile updates. The sync machinary can this stream to understand whether a profile update needs to be included in a client's sync response.
Add a new stream type for profile updates. This allows sync processes to determine which profile updates a given user has or hasn't seen yet.
Replicate changes to the profile updates stream so that sync workers know there's a new profile update, and wake up sync waiters accordingly. Updates are keyed by `room_id` - sync waiters should only wake up if a user they share a room with updates their profile.
Based on the provided since token and filter.
And update relevant documentation/integration test config.
Prevent the `ProfileRestServlet` from handling requests intended for the `ProfileFieldRestServlet`. If a requester tried to call PUT `.../profile/@user:domain/(key_id?)` a worker that did not mount `ProfileFieldRestServlet`, they would then receive a `405 Method Not Allowed` instead of the expected `404 Not Found`.
a309372 to
f27ca70
Compare
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
anoadragon453
left a comment
There was a problem hiding this comment.
LGTM otherwise (but I can't approve my own PRs).
Co-authored-by: Andrew Morgan <1342360+anoadragon453@users.noreply.github.com>
reivilibre
left a comment
There was a problem hiding this comment.
(haven't finished my review but reached end of day; midway through synapse/handlers/sync.py)
| if stream_id and profile_update_targets["rooms"]: | ||
| self._notifier.on_new_event( | ||
| StreamKeyType.PROFILE_UPDATES, | ||
| stream_id, |
There was a problem hiding this comment.
Hmmm. I think there's actually a problem here, but luckily it seems harmless for the current streams that seem to be doing it wrong: as far as I can tell, the returned stream_id is the highest stream ID of those just persisted by this transaction.
But that doesn't guarantee that the stream is actually at that position yet and I think on_new_event actually wants the actual new position as its new_token. (This only gets used for real purposes for a small handful of stream types)
I think this is supposed to rather be the get_current_token.
will track the other streams doing this wrong at #19969
Wonder what happens in a 2-writer setup; does on_new_event get triggered locally AND by the stream following? :S
There was a problem hiding this comment.
Is it ok if we leave this for #19969 to handle, given there seems to be a bit of uncertainty involved?
| # ExpiringCache((User, Device)) | ||
| # -> LruCache( | ||
| # sha256(Other User ID + Field Name + Field value) -> bool | ||
| # ) | ||
| self.lazy_loaded_profile_fields_cache: ExpiringCache[ | ||
| tuple[str, str | None], LruCache[str, bool] | ||
| ] = ExpiringCache( |
There was a problem hiding this comment.
Hmm, a few concerns with this cache:
- if I set my field (1) A=1, then (2) A=2, then (3) A=1 again, does my 3rd update get skipped because the hash matches an existing entry in cache? It seems you'd have to rekey this cache like
sha256(other_user_id || field_name) -> sha256(field value) - we could store the hash bytes as
bytesfor 50% memory savings :) - blake2b might be a preferable choice of hash (constraining ourselves to the
hashlib) for performance whilst retaining the collision resistance property; I think an output size of 16 bytes (128b) would be fine (whilst saving us memory) and since it's a keyed hash, we can generate a random key on every startup (e.g. as a constant in this file) so that it's not externally predictable.
Separately, there is a doubt in my mind: what happens if someone repeats a sync exactly, because the HTTP response gets lost and so the request gets retried?
It sounds like by that point, the cache has already been updated and therefore the profile update is lost from the second sync.
Most of the time we (most deployments) have a ResponseCache so for 2 minutes the repeated sync will be fine, but 'hmm'.
If this is the same as what lazy_loaded_members_cache does then maybe we add a TODO: comment + issue and move on since that by itself might be a bigger problem.
Co-authored-by: Olivier 'reivilibre' <olivier@librepush.net>
Instead of passing in a previous membership or a list of target users down in the event internal metadata, use the state delta (or rather, previously calculated sliding sync table changes based on state delta) to figure out if a membership really changed. Then add the member lookups to get the right users to update for. Somewhat WIP, a few tests still fail and this commit misses a bunch of docstrings and/or cleanups of old functionality.
* Should also notify on KNOCK and INVITE (though calling them a join is a bit poor) * Remove unnecessary fetch of user rooms as we only need to notify the members of the current room in question
The MSC indicates we should send down profiles for room members when someone joins a room, but it's a bit of a question right now on how to do this.
…deactivation` We'll anyway have "left room" actions generated for the user for everyone who is interested, as the user parter pushes them out of the room. Thus we would be doing unnecessary extra work here.
We now calculate the targets when persisting the updates.
|
|
||
| if self._msc4429_enabled: | ||
| # Handle changes to the profile updates stream. | ||
| # We've already done a bunch of work calculating the changes needed |
There was a problem hiding this comment.
As discussed elsewhere, we should, assuming this is kept in this pr, track figuring out this properly in further pull requests, specifically:
- is this actually catching all the possible situations, especially regarding a user being removed from a room?
- can we get rid of piggybacking on the sliding sync table change results, since it's kind of ugly, and instead refactor both to use some common functionality?
This PR implements support for MSC4429: Profile Updates for Legacy Sync.
Recommended to review commit-by-commit.Commit history has become messy, suggest reviewing as a single item.Paired with matrix-org/complement#849 and branch https://github.com/matrix-org/sytest/tree/anoa/msc4429
Tracking issue for removing unstable identifiers in Synapse: #19891
Pull Request Checklist
EventStoretoEventWorkerStore.".code blocks.