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

Commit 5099b5e

Browse files
authored
Use new device_list_changes_in_room table when getting device list changes (#13045)
1 parent c6d6176 commit 5099b5e

File tree

4 files changed

+117
-31
lines changed

4 files changed

+117
-31
lines changed

changelog.d/13045.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Speed up fetching of device list changes in `/sync` and `/keys/changes`.

synapse/handlers/device.py

Lines changed: 52 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -123,23 +123,28 @@ async def get_device(self, user_id: str, device_id: str) -> JsonDict:
123123

124124
return device
125125

126-
@trace
127-
@measure_func("device.get_user_ids_changed")
128-
async def get_user_ids_changed(
129-
self, user_id: str, from_token: StreamToken
130-
) -> JsonDict:
131-
"""Get list of users that have had the devices updated, or have newly
132-
joined a room, that `user_id` may be interested in.
126+
async def get_device_changes_in_shared_rooms(
127+
self, user_id: str, room_ids: Collection[str], from_token: StreamToken
128+
) -> Collection[str]:
129+
"""Get the set of users whose devices have changed who share a room with
130+
the given user.
133131
"""
132+
changed_users = await self.store.get_device_list_changes_in_rooms(
133+
room_ids, from_token.device_list_key
134+
)
134135

135-
set_tag("user_id", user_id)
136-
set_tag("from_token", from_token)
137-
now_room_key = self.store.get_room_max_token()
136+
if changed_users is not None:
137+
# We also check if the given user has changed their device. If
138+
# they're in no rooms then the above query won't include them.
139+
changed = await self.store.get_users_whose_devices_changed(
140+
from_token.device_list_key, [user_id]
141+
)
142+
changed_users.update(changed)
143+
return changed_users
138144

139-
room_ids = await self.store.get_rooms_for_user(user_id)
145+
# If the DB returned None then the `from_token` is too old, so we fall
146+
# back on looking for device updates for all users.
140147

141-
# First we check if any devices have changed for users that we share
142-
# rooms with.
143148
users_who_share_room = await self.store.get_users_who_share_room_with_user(
144149
user_id
145150
)
@@ -153,6 +158,27 @@ async def get_user_ids_changed(
153158
from_token.device_list_key, tracked_users
154159
)
155160

161+
return changed
162+
163+
@trace
164+
@measure_func("device.get_user_ids_changed")
165+
async def get_user_ids_changed(
166+
self, user_id: str, from_token: StreamToken
167+
) -> JsonDict:
168+
"""Get list of users that have had the devices updated, or have newly
169+
joined a room, that `user_id` may be interested in.
170+
"""
171+
172+
set_tag("user_id", user_id)
173+
set_tag("from_token", from_token)
174+
now_room_key = self.store.get_room_max_token()
175+
176+
room_ids = await self.store.get_rooms_for_user(user_id)
177+
178+
changed = await self.get_device_changes_in_shared_rooms(
179+
user_id, room_ids, from_token
180+
)
181+
156182
# Then work out if any users have since joined
157183
rooms_changed = self.store.get_rooms_that_changed(room_ids, from_token.room_key)
158184

@@ -237,10 +263,19 @@ async def get_user_ids_changed(
237263
break
238264

239265
if possibly_changed or possibly_left:
240-
# Take the intersection of the users whose devices may have changed
241-
# and those that actually still share a room with the user
242-
possibly_joined = possibly_changed & users_who_share_room
243-
possibly_left = (possibly_changed | possibly_left) - users_who_share_room
266+
possibly_joined = possibly_changed
267+
possibly_left = possibly_changed | possibly_left
268+
269+
# Double check if we still share rooms with the given user.
270+
users_rooms = await self.store.get_rooms_for_users_with_stream_ordering(
271+
possibly_left
272+
)
273+
for changed_user_id, entries in users_rooms.items():
274+
if any(e.room_id in room_ids for e in entries):
275+
possibly_left.discard(changed_user_id)
276+
else:
277+
possibly_joined.discard(changed_user_id)
278+
244279
else:
245280
possibly_joined = set()
246281
possibly_left = set()

synapse/handlers/sync.py

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@ def __init__(self, hs: "HomeServer"):
240240
self.auth_blocking = hs.get_auth_blocking()
241241
self._storage_controllers = hs.get_storage_controllers()
242242
self._state_storage_controller = self._storage_controllers.state
243+
self._device_handler = hs.get_device_handler()
243244

244245
# TODO: flush cache entries on subsequent sync request.
245246
# Once we get the next /sync request (ie, one with the same access token
@@ -1268,21 +1269,11 @@ async def _generate_sync_entry_for_device_list(
12681269
):
12691270
users_that_have_changed.add(changed_user_id)
12701271
else:
1271-
users_who_share_room = (
1272-
await self.store.get_users_who_share_room_with_user(user_id)
1273-
)
1274-
1275-
# Always tell the user about their own devices. We check as the user
1276-
# ID is almost certainly already included (unless they're not in any
1277-
# rooms) and taking a copy of the set is relatively expensive.
1278-
if user_id not in users_who_share_room:
1279-
users_who_share_room = set(users_who_share_room)
1280-
users_who_share_room.add(user_id)
1281-
1282-
tracked_users = users_who_share_room
12831272
users_that_have_changed = (
1284-
await self.store.get_users_whose_devices_changed(
1285-
since_token.device_list_key, tracked_users
1273+
await self._device_handler.get_device_changes_in_shared_rooms(
1274+
user_id,
1275+
sync_result_builder.joined_room_ids,
1276+
from_token=since_token,
12861277
)
12871278
)
12881279

synapse/storage/databases/main/devices.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1208,6 +1208,65 @@ def get_devices_not_accessed_since_txn(
12081208

12091209
return devices
12101210

1211+
@cached()
1212+
async def _get_min_device_lists_changes_in_room(self) -> int:
1213+
"""Returns the minimum stream ID that we have entries for
1214+
`device_lists_changes_in_room`
1215+
"""
1216+
1217+
return await self.db_pool.simple_select_one_onecol(
1218+
table="device_lists_changes_in_room",
1219+
keyvalues={},
1220+
retcol="COALESCE(MIN(stream_id), 0)",
1221+
desc="get_min_device_lists_changes_in_room",
1222+
)
1223+
1224+
async def get_device_list_changes_in_rooms(
1225+
self, room_ids: Collection[str], from_id: int
1226+
) -> Optional[Set[str]]:
1227+
"""Return the set of users whose devices have changed in the given rooms
1228+
since the given stream ID.
1229+
1230+
Returns None if the given stream ID is too old.
1231+
"""
1232+
1233+
if not room_ids:
1234+
return set()
1235+
1236+
min_stream_id = await self._get_min_device_lists_changes_in_room()
1237+
1238+
if min_stream_id > from_id:
1239+
return None
1240+
1241+
sql = """
1242+
SELECT DISTINCT user_id FROM device_lists_changes_in_room
1243+
WHERE {clause} AND stream_id >= ?
1244+
"""
1245+
1246+
def _get_device_list_changes_in_rooms_txn(
1247+
txn: LoggingTransaction,
1248+
clause,
1249+
args,
1250+
) -> Set[str]:
1251+
txn.execute(sql.format(clause=clause), args)
1252+
return {user_id for user_id, in txn}
1253+
1254+
changes = set()
1255+
for chunk in batch_iter(room_ids, 1000):
1256+
clause, args = make_in_list_sql_clause(
1257+
self.database_engine, "room_id", chunk
1258+
)
1259+
args.append(from_id)
1260+
1261+
changes |= await self.db_pool.runInteraction(
1262+
"get_device_list_changes_in_rooms",
1263+
_get_device_list_changes_in_rooms_txn,
1264+
clause,
1265+
args,
1266+
)
1267+
1268+
return changes
1269+
12111270

12121271
class DeviceBackgroundUpdateStore(SQLBaseStore):
12131272
def __init__(

0 commit comments

Comments
 (0)