Prevent updating display name causing time outs on large accounts#17074
Prevent updating display name causing time outs on large accounts#17074neilisfragile wants to merge 9 commits into
Conversation
60b9999 to
8e6b608
Compare
…pt, so that the lag in updating all rooms is less noticable
… profiles and avatars
1c058ff to
aa147ed
Compare
| # This allows us to verify that set_displayname doesn't block on room updates | ||
| from twisted.internet.defer import ensureDeferred | ||
|
|
||
| displayname_deferred = ensureDeferred( |
There was a problem hiding this comment.
Is this the correct way to test that the method returns immediately? Using onSuccess caused the reactor to tick forwards.
There was a problem hiding this comment.
The problem is "immediately" is relative. We expect to return after a little bit of work of scheduling a background task but not all of the work of updating all of the rooms like it did before.
Currently, this doesn't do what you want and we don't even wait for the work to be scheduled.
What we should probably do is just self.get_success(self.handler.set_displayname(...)) as we do expect this function to return before doing any work, check to see that no rooms have updated yet, then self.pump() until we see some updates.
Also see tests/util/test_task_scheduler.py and tests/rest/admin/test_room.py where we wait for the task scheduler to complete things.
| if deactivation: | ||
| # During deactivation, run profile updates synchronously to ensure | ||
| # they complete before room forgetting logic runs | ||
| await self._update_join_states_direct(requester, target_user) |
There was a problem hiding this comment.
I need to do this because deactivate account expects profiles and avatars to have been removed before deactivation. I could not see another way to make the flow wait for the background profile updates to complete, so opted for two versions of the update method.
Is there a better way?
There was a problem hiding this comment.
To make this more clear, we could have a separate update_in_background argument.
(better name welcome)
|
PR now ready for review, there are two specific points I would like some feedback on, I have left comments inline. Disclaimer: I used Claude Code in a consultative fashion to generate parts of this PR. |
| LEFT JOIN receipts_linearized rl ON ( | ||
| rl.room_id = cse.room_id | ||
| AND rl.user_id = ? | ||
| AND rl.receipt_type = 'm.read' |
There was a problem hiding this comment.
We should probably take into account the other read receipt types, m.read.private and m.fully_read
Both seem applicable for sorting by last activity
| LEFT JOIN receipts_linearized rl ON ( | ||
| rl.room_id = cse.room_id | ||
| AND rl.user_id = ? | ||
| AND rl.receipt_type = 'm.read' |
There was a problem hiding this comment.
| AND rl.receipt_type = 'm.read' | |
| AND rl.receipt_type = ? |
To avoid typos, we can pass in our constants (ReceiptTypes.READ) as args
| rl.room_id = cse.room_id | ||
| AND rl.user_id = ? | ||
| AND rl.receipt_type = 'm.read' |
There was a problem hiding this comment.
To confirm, we do have some indexes that I think will cover this (receipts_linearized_unique_index/receipts_linearized_uniqueness_thread):
$ psql synapse
> \d+ receipts_linearized
[...]
Indexes:
"receipts_linearized_event_id" btree (room_id, event_id)
"receipts_linearized_id" btree (stream_id)
"receipts_linearized_room_stream" btree (room_id, stream_id)
"receipts_linearized_unique_index" UNIQUE, btree (room_id, receipt_type, user_id) WHERE thread_id IS NULL
"receipts_linearized_uniqueness_thread" UNIQUE CONSTRAINT, btree (room_id, receipt_type, user_id, thread_id)
"receipts_linearized_user" btree (user_id)| ) | ||
| WHERE cse.type = 'm.room.member' | ||
| AND cse.membership = 'join' | ||
| AND cse.state_key = ? |
There was a problem hiding this comment.
| AND cse.state_key = ? |
I don't think we need to check state_key since membership will be the the source of truth here.
You can see an example where we don't check it here:
synapse/synapse/storage/databases/main/roommember.py
Lines 168 to 188 in b2997a8
And here is where we insert the data which uses the room_memberships table to populate current_state_events
synapse/synapse/storage/databases/main/events.py
Lines 1869 to 1885 in b2997a8
| AND rl.receipt_type = 'm.read' | ||
| ) | ||
| WHERE cse.type = 'm.room.member' | ||
| AND cse.membership = 'join' |
There was a problem hiding this comment.
| AND cse.membership = 'join' | |
| AND cse.membership = ? |
Membership.JOIN
| Iterates through all rooms where the target user is currently joined and | ||
| updates their membership event to reflect the new profile information. |
There was a problem hiding this comment.
This uses a lot of words to say pretty much nothing more than above.
| Implementation Details: | ||
| - Rooms are processed in read receipt order (most recently viewed first) | ||
| - Each membership update is treated as a "join" event with new profile data | ||
| - Rate limiting is disabled for these updates to hide that they're not atomic | ||
| - Failures in individual rooms are logged but don't affect other rooms | ||
| - Assumes target_user is not a guest (guests can't set profile data) |
There was a problem hiding this comment.
The spec says they can:
The following API endpoints are allowed to be accessed by guest accounts for their own account maintenance:
[...]
-- https://spec.matrix.org/v1.14/client-server-api/#guest-access
| Note: | ||
| This stomps over any custom display name or avatar URL in member events. |
There was a problem hiding this comment.
The note is weird to have below the description above
| if deactivation: | ||
| # During deactivation, run profile updates synchronously to ensure | ||
| # they complete before room forgetting logic runs | ||
| await self._update_join_states_direct(requester, target_user) |
There was a problem hiding this comment.
To make this more clear, we could have a separate update_in_background argument.
(better name welcome)
| @@ -236,7 +252,18 @@ async def set_displayname( | |||
| ) | |||
|
|
|||
There was a problem hiding this comment.
There are so many arguments for this function and multiple of the same type right next to each other that could easily be confused if you write them without keywords.
We should force keywords on set_displayname and set_avatar_url with the *. See example here
|
Superseded by #19311. |
|
Well that will teach me to sit on a PR for months! |
|
@neilisfragile You could still open a new PR with the sorting changes you made. Here is how I described the benefit:
|
This will be slightly tricky as the new background task processes rooms by alphabetical ordering of room ID: synapse/synapse/handlers/profile.py Lines 727 to 735 in ae23928 This was chosen to allow easily resuming the work after a crash/pause in the background task process, and is relatively stable (other than rooms that are joined partway-through). In contrast, ordering by latest activity is far less stable. Doing so may require storing the ordering of rooms in a temporary table at the beginning of the profile update in order to allow resumption after a homeserver crash/restart. |
|
okay, I'll wait for it to land on matrix.org and then try it with my account (as a pretty large one) to get a sense for the perceived experience. Another approach could just be to take the top 20 (say), apply that, then work through the main list and filter those 20 as you come from them. If there is a restart, then the worst that happens is that the top 20 records are lost and the rename is applied twice (and is probably a no-op) |
|
@neilisfragile the change has landed on matrix.org as of 13:00 UK today. Please give it a try and see what you think!
Yes, that sounds like it could work. |
Fixes #1297
Pull Request Checklist
EventStoretoEventWorkerStore.".code blocks.(run the linters)