Skip to content
This repository was archived by the owner on Apr 26, 2024. It is now read-only.

Commit 2b5643b

Browse files
authored
Optimise calculating device_list changes in /sync. (#11974)
For users with large accounts it is inefficient to calculate the set of users they share a room with (and takes a lot of space in the cache). Instead we can look at users whose devices have changed since the last sync and check if they share a room with the syncing user.
1 parent bab2394 commit 2b5643b

File tree

4 files changed

+126
-15
lines changed

4 files changed

+126
-15
lines changed

changelog.d/11974.misc

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Optimise calculating device_list changes in `/sync`.

synapse/handlers/sync.py

+53-15
Original file line numberDiff line numberDiff line change
@@ -1289,23 +1289,54 @@ async def _generate_sync_entry_for_device_list(
12891289
# room with by looking at all users that have left a room plus users
12901290
# that were in a room we've left.
12911291

1292-
users_who_share_room = await self.store.get_users_who_share_room_with_user(
1293-
user_id
1294-
)
1295-
1296-
# Always tell the user about their own devices. We check as the user
1297-
# ID is almost certainly already included (unless they're not in any
1298-
# rooms) and taking a copy of the set is relatively expensive.
1299-
if user_id not in users_who_share_room:
1300-
users_who_share_room = set(users_who_share_room)
1301-
users_who_share_room.add(user_id)
1292+
users_that_have_changed = set()
13021293

1303-
tracked_users = users_who_share_room
1294+
joined_rooms = sync_result_builder.joined_room_ids
13041295

1305-
# Step 1a, check for changes in devices of users we share a room with
1306-
users_that_have_changed = await self.store.get_users_whose_devices_changed(
1307-
since_token.device_list_key, tracked_users
1296+
# Step 1a, check for changes in devices of users we share a room
1297+
# with
1298+
#
1299+
# We do this in two different ways depending on what we have cached.
1300+
# If we already have a list of all the user that have changed since
1301+
# the last sync then it's likely more efficient to compare the rooms
1302+
# they're in with the rooms the syncing user is in.
1303+
#
1304+
# If we don't have that info cached then we get all the users that
1305+
# share a room with our user and check if those users have changed.
1306+
changed_users = self.store.get_cached_device_list_changes(
1307+
since_token.device_list_key
13081308
)
1309+
if changed_users is not None:
1310+
result = await self.store.get_rooms_for_users_with_stream_ordering(
1311+
changed_users
1312+
)
1313+
1314+
for changed_user_id, entries in result.items():
1315+
# Check if the changed user shares any rooms with the user,
1316+
# or if the changed user is the syncing user (as we always
1317+
# want to include device list updates of their own devices).
1318+
if user_id == changed_user_id or any(
1319+
e.room_id in joined_rooms for e in entries
1320+
):
1321+
users_that_have_changed.add(changed_user_id)
1322+
else:
1323+
users_who_share_room = (
1324+
await self.store.get_users_who_share_room_with_user(user_id)
1325+
)
1326+
1327+
# Always tell the user about their own devices. We check as the user
1328+
# ID is almost certainly already included (unless they're not in any
1329+
# rooms) and taking a copy of the set is relatively expensive.
1330+
if user_id not in users_who_share_room:
1331+
users_who_share_room = set(users_who_share_room)
1332+
users_who_share_room.add(user_id)
1333+
1334+
tracked_users = users_who_share_room
1335+
users_that_have_changed = (
1336+
await self.store.get_users_whose_devices_changed(
1337+
since_token.device_list_key, tracked_users
1338+
)
1339+
)
13091340

13101341
# Step 1b, check for newly joined rooms
13111342
for room_id in newly_joined_rooms:
@@ -1329,7 +1360,14 @@ async def _generate_sync_entry_for_device_list(
13291360
newly_left_users.update(left_users)
13301361

13311362
# Remove any users that we still share a room with.
1332-
newly_left_users -= users_who_share_room
1363+
left_users_rooms = (
1364+
await self.store.get_rooms_for_users_with_stream_ordering(
1365+
newly_left_users
1366+
)
1367+
)
1368+
for user_id, entries in left_users_rooms.items():
1369+
if any(e.room_id in joined_rooms for e in entries):
1370+
newly_left_users.discard(user_id)
13331371

13341372
return DeviceLists(changed=users_that_have_changed, left=newly_left_users)
13351373
else:

synapse/storage/databases/main/devices.py

+10
Original file line numberDiff line numberDiff line change
@@ -670,6 +670,16 @@ async def get_cached_devices_for_user(self, user_id: str) -> Dict[str, JsonDict]
670670
device["device_id"]: db_to_json(device["content"]) for device in devices
671671
}
672672

673+
def get_cached_device_list_changes(
674+
self,
675+
from_key: int,
676+
) -> Optional[Set[str]]:
677+
"""Get set of users whose devices have changed since `from_key`, or None
678+
if that information is not in our cache.
679+
"""
680+
681+
return self._device_list_stream_cache.get_all_entities_changed(from_key)
682+
673683
async def get_users_whose_devices_changed(
674684
self, from_key: int, user_ids: Iterable[str]
675685
) -> Set[str]:

synapse/storage/databases/main/roommember.py

+62
Original file line numberDiff line numberDiff line change
@@ -504,6 +504,68 @@ def _get_rooms_for_user_with_stream_ordering_txn(
504504
for room_id, instance, stream_id in txn
505505
)
506506

507+
@cachedList(
508+
cached_method_name="get_rooms_for_user_with_stream_ordering",
509+
list_name="user_ids",
510+
)
511+
async def get_rooms_for_users_with_stream_ordering(
512+
self, user_ids: Collection[str]
513+
) -> Dict[str, FrozenSet[GetRoomsForUserWithStreamOrdering]]:
514+
"""A batched version of `get_rooms_for_user_with_stream_ordering`.
515+
516+
Returns:
517+
Map from user_id to set of rooms that is currently in.
518+
"""
519+
return await self.db_pool.runInteraction(
520+
"get_rooms_for_users_with_stream_ordering",
521+
self._get_rooms_for_users_with_stream_ordering_txn,
522+
user_ids,
523+
)
524+
525+
def _get_rooms_for_users_with_stream_ordering_txn(
526+
self, txn, user_ids: Collection[str]
527+
) -> Dict[str, FrozenSet[GetRoomsForUserWithStreamOrdering]]:
528+
529+
clause, args = make_in_list_sql_clause(
530+
self.database_engine,
531+
"c.state_key",
532+
user_ids,
533+
)
534+
535+
if self._current_state_events_membership_up_to_date:
536+
sql = f"""
537+
SELECT c.state_key, room_id, e.instance_name, e.stream_ordering
538+
FROM current_state_events AS c
539+
INNER JOIN events AS e USING (room_id, event_id)
540+
WHERE
541+
c.type = 'm.room.member'
542+
AND c.membership = ?
543+
AND {clause}
544+
"""
545+
else:
546+
sql = f"""
547+
SELECT c.state_key, room_id, e.instance_name, e.stream_ordering
548+
FROM current_state_events AS c
549+
INNER JOIN room_memberships AS m USING (room_id, event_id)
550+
INNER JOIN events AS e USING (room_id, event_id)
551+
WHERE
552+
c.type = 'm.room.member'
553+
AND m.membership = ?
554+
AND {clause}
555+
"""
556+
557+
txn.execute(sql, [Membership.JOIN] + args)
558+
559+
result = {user_id: set() for user_id in user_ids}
560+
for user_id, room_id, instance, stream_id in txn:
561+
result[user_id].add(
562+
GetRoomsForUserWithStreamOrdering(
563+
room_id, PersistedEventPosition(instance, stream_id)
564+
)
565+
)
566+
567+
return {user_id: frozenset(v) for user_id, v in result.items()}
568+
507569
async def get_users_server_still_shares_room_with(
508570
self, user_ids: Collection[str]
509571
) -> Set[str]:

0 commit comments

Comments
 (0)