Skip unused calculations in sync handler. (#14908)

If a sync request does not need to calculate per-room entries &
is not generating presence & is not generating device list data
(e.g. during initial sync) avoid the expensive calculation of room
specific data.

This is a micro-optimisation for clients syncing simply to receive
to-device information.
This commit is contained in:
Patrick Cloke 2023-02-02 13:45:12 -05:00 committed by GitHub
parent f36da501be
commit da05b70af5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 141 additions and 129 deletions

1
changelog.d/14908.misc Normal file
View File

@ -0,0 +1 @@
Improve performance of `/sync` in a few situations.

View File

@ -283,6 +283,9 @@ class FilterCollection:
await self._room_filter.filter(events) await self._room_filter.filter(events)
) )
def blocks_all_rooms(self) -> bool:
return self._room_filter.filters_all_rooms()
def blocks_all_presence(self) -> bool: def blocks_all_presence(self) -> bool:
return ( return (
self._presence_filter.filters_all_types() self._presence_filter.filters_all_types()

View File

@ -1448,41 +1448,67 @@ class SyncHandler:
sync_result_builder sync_result_builder
) )
logger.debug("Fetching room data")
(
newly_joined_rooms,
newly_joined_or_invited_or_knocked_users,
newly_left_rooms,
newly_left_users,
) = await self._generate_sync_entry_for_rooms(
sync_result_builder, account_data_by_room
)
# Presence data is included if the server has it enabled and not filtered out. # Presence data is included if the server has it enabled and not filtered out.
include_presence_data = ( include_presence_data = bool(
self.hs_config.server.use_presence self.hs_config.server.use_presence
and not sync_config.filter_collection.blocks_all_presence() and not sync_config.filter_collection.blocks_all_presence()
) )
if include_presence_data: # Device list updates are sent if a since token is provided.
logger.debug("Fetching presence data") include_device_list_updates = bool(since_token and since_token.device_list_key)
await self._generate_sync_entry_for_presence(
sync_result_builder, # If we do not care about the rooms or things which depend on the room
# data (namely presence and device list updates), then we can skip
# this process completely.
device_lists = DeviceListUpdates()
if (
not sync_result_builder.sync_config.filter_collection.blocks_all_rooms()
or include_presence_data
or include_device_list_updates
):
logger.debug("Fetching room data")
# Note that _generate_sync_entry_for_rooms sets sync_result_builder.joined, which
# is used in calculate_user_changes below.
(
newly_joined_rooms, newly_joined_rooms,
newly_joined_or_invited_or_knocked_users, newly_left_rooms,
) = await self._generate_sync_entry_for_rooms(
sync_result_builder, account_data_by_room
) )
# Work out which users have joined or left rooms we're in. We use this
# to build the presence and device_list parts of the sync response in
# `_generate_sync_entry_for_presence` and
# `_generate_sync_entry_for_device_list` respectively.
if include_presence_data or include_device_list_updates:
# This uses the sync_result_builder.joined which is set in
# `_generate_sync_entry_for_rooms`, if that didn't find any joined
# rooms for some reason it is a no-op.
(
newly_joined_or_invited_or_knocked_users,
newly_left_users,
) = sync_result_builder.calculate_user_changes()
if include_presence_data:
logger.debug("Fetching presence data")
await self._generate_sync_entry_for_presence(
sync_result_builder,
newly_joined_rooms,
newly_joined_or_invited_or_knocked_users,
)
if include_device_list_updates:
device_lists = await self._generate_sync_entry_for_device_list(
sync_result_builder,
newly_joined_rooms=newly_joined_rooms,
newly_joined_or_invited_or_knocked_users=newly_joined_or_invited_or_knocked_users,
newly_left_rooms=newly_left_rooms,
newly_left_users=newly_left_users,
)
logger.debug("Fetching to-device data") logger.debug("Fetching to-device data")
await self._generate_sync_entry_for_to_device(sync_result_builder) await self._generate_sync_entry_for_to_device(sync_result_builder)
device_lists = await self._generate_sync_entry_for_device_list(
sync_result_builder,
newly_joined_rooms=newly_joined_rooms,
newly_joined_or_invited_or_knocked_users=newly_joined_or_invited_or_knocked_users,
newly_left_rooms=newly_left_rooms,
newly_left_users=newly_left_users,
)
logger.debug("Fetching OTK data") logger.debug("Fetching OTK data")
device_id = sync_config.device_id device_id = sync_config.device_id
one_time_keys_count: JsonDict = {} one_time_keys_count: JsonDict = {}
@ -1551,6 +1577,7 @@ class SyncHandler:
user_id = sync_result_builder.sync_config.user.to_string() user_id = sync_result_builder.sync_config.user.to_string()
since_token = sync_result_builder.since_token since_token = sync_result_builder.since_token
assert since_token is not None
# Take a copy since these fields will be mutated later. # Take a copy since these fields will be mutated later.
newly_joined_or_invited_or_knocked_users = set( newly_joined_or_invited_or_knocked_users = set(
@ -1558,92 +1585,85 @@ class SyncHandler:
) )
newly_left_users = set(newly_left_users) newly_left_users = set(newly_left_users)
if since_token and since_token.device_list_key: # We want to figure out what user IDs the client should refetch
# We want to figure out what user IDs the client should refetch # device keys for, and which users we aren't going to track changes
# device keys for, and which users we aren't going to track changes # for anymore.
# for anymore. #
# # For the first step we check:
# For the first step we check: # a. if any users we share a room with have updated their devices,
# a. if any users we share a room with have updated their devices, # and
# and # b. we also check if we've joined any new rooms, or if a user has
# b. we also check if we've joined any new rooms, or if a user has # joined a room we're in.
# joined a room we're in. #
# # For the second step we just find any users we no longer share a
# For the second step we just find any users we no longer share a # room with by looking at all users that have left a room plus users
# room with by looking at all users that have left a room plus users # that were in a room we've left.
# that were in a room we've left.
users_that_have_changed = set() users_that_have_changed = set()
joined_rooms = sync_result_builder.joined_room_ids joined_rooms = sync_result_builder.joined_room_ids
# Step 1a, check for changes in devices of users we share a room # Step 1a, check for changes in devices of users we share a room
# with # with
# #
# We do this in two different ways depending on what we have cached. # We do this in two different ways depending on what we have cached.
# If we already have a list of all the user that have changed since # If we already have a list of all the user that have changed since
# the last sync then it's likely more efficient to compare the rooms # the last sync then it's likely more efficient to compare the rooms
# they're in with the rooms the syncing user is in. # they're in with the rooms the syncing user is in.
# #
# If we don't have that info cached then we get all the users that # If we don't have that info cached then we get all the users that
# share a room with our user and check if those users have changed. # share a room with our user and check if those users have changed.
cache_result = self.store.get_cached_device_list_changes( cache_result = self.store.get_cached_device_list_changes(
since_token.device_list_key since_token.device_list_key
) )
if cache_result.hit: if cache_result.hit:
changed_users = cache_result.entities changed_users = cache_result.entities
result = await self.store.get_rooms_for_users(changed_users) result = await self.store.get_rooms_for_users(changed_users)
for changed_user_id, entries in result.items(): for changed_user_id, entries in result.items():
# Check if the changed user shares any rooms with the user, # Check if the changed user shares any rooms with the user,
# or if the changed user is the syncing user (as we always # or if the changed user is the syncing user (as we always
# want to include device list updates of their own devices). # want to include device list updates of their own devices).
if user_id == changed_user_id or any( if user_id == changed_user_id or any(
rid in joined_rooms for rid in entries rid in joined_rooms for rid in entries
): ):
users_that_have_changed.add(changed_user_id) users_that_have_changed.add(changed_user_id)
else:
users_that_have_changed = (
await self._device_handler.get_device_changes_in_shared_rooms(
user_id,
sync_result_builder.joined_room_ids,
from_token=since_token,
)
)
# Step 1b, check for newly joined rooms
for room_id in newly_joined_rooms:
joined_users = await self.store.get_users_in_room(room_id)
newly_joined_or_invited_or_knocked_users.update(joined_users)
# TODO: Check that these users are actually new, i.e. either they
# weren't in the previous sync *or* they left and rejoined.
users_that_have_changed.update(newly_joined_or_invited_or_knocked_users)
user_signatures_changed = (
await self.store.get_users_whose_signatures_changed(
user_id, since_token.device_list_key
)
)
users_that_have_changed.update(user_signatures_changed)
# Now find users that we no longer track
for room_id in newly_left_rooms:
left_users = await self.store.get_users_in_room(room_id)
newly_left_users.update(left_users)
# Remove any users that we still share a room with.
left_users_rooms = await self.store.get_rooms_for_users(newly_left_users)
for user_id, entries in left_users_rooms.items():
if any(rid in joined_rooms for rid in entries):
newly_left_users.discard(user_id)
return DeviceListUpdates(
changed=users_that_have_changed, left=newly_left_users
)
else: else:
return DeviceListUpdates() users_that_have_changed = (
await self._device_handler.get_device_changes_in_shared_rooms(
user_id,
sync_result_builder.joined_room_ids,
from_token=since_token,
)
)
# Step 1b, check for newly joined rooms
for room_id in newly_joined_rooms:
joined_users = await self.store.get_users_in_room(room_id)
newly_joined_or_invited_or_knocked_users.update(joined_users)
# TODO: Check that these users are actually new, i.e. either they
# weren't in the previous sync *or* they left and rejoined.
users_that_have_changed.update(newly_joined_or_invited_or_knocked_users)
user_signatures_changed = await self.store.get_users_whose_signatures_changed(
user_id, since_token.device_list_key
)
users_that_have_changed.update(user_signatures_changed)
# Now find users that we no longer track
for room_id in newly_left_rooms:
left_users = await self.store.get_users_in_room(room_id)
newly_left_users.update(left_users)
# Remove any users that we still share a room with.
left_users_rooms = await self.store.get_rooms_for_users(newly_left_users)
for user_id, entries in left_users_rooms.items():
if any(rid in joined_rooms for rid in entries):
newly_left_users.discard(user_id)
return DeviceListUpdates(changed=users_that_have_changed, left=newly_left_users)
@trace @trace
async def _generate_sync_entry_for_to_device( async def _generate_sync_entry_for_to_device(
@ -1720,6 +1740,7 @@ class SyncHandler:
since_token = sync_result_builder.since_token since_token = sync_result_builder.since_token
if since_token and not sync_result_builder.full_state: if since_token and not sync_result_builder.full_state:
# TODO Do not fetch room account data if it will be unused.
( (
global_account_data, global_account_data,
account_data_by_room, account_data_by_room,
@ -1736,6 +1757,7 @@ class SyncHandler:
sync_config.user sync_config.user
) )
else: else:
# TODO Do not fetch room account data if it will be unused.
( (
global_account_data, global_account_data,
account_data_by_room, account_data_by_room,
@ -1818,7 +1840,7 @@ class SyncHandler:
self, self,
sync_result_builder: "SyncResultBuilder", sync_result_builder: "SyncResultBuilder",
account_data_by_room: Dict[str, Dict[str, JsonDict]], account_data_by_room: Dict[str, Dict[str, JsonDict]],
) -> Tuple[AbstractSet[str], AbstractSet[str], AbstractSet[str], AbstractSet[str]]: ) -> Tuple[AbstractSet[str], AbstractSet[str]]:
"""Generates the rooms portion of the sync response. Populates the """Generates the rooms portion of the sync response. Populates the
`sync_result_builder` with the result. `sync_result_builder` with the result.
@ -1831,24 +1853,22 @@ class SyncHandler:
account_data_by_room: Dictionary of per room account data account_data_by_room: Dictionary of per room account data
Returns: Returns:
Returns a 4-tuple describing rooms the user has joined or left, and users who've Returns a 2-tuple describing rooms the user has joined or left.
joined or left rooms any rooms the user is in. This gets used later in
`_generate_sync_entry_for_device_list`.
Its entries are: Its entries are:
- newly_joined_rooms - newly_joined_rooms
- newly_joined_or_invited_or_knocked_users
- newly_left_rooms - newly_left_rooms
- newly_left_users
""" """
since_token = sync_result_builder.since_token since_token = sync_result_builder.since_token
user_id = sync_result_builder.sync_config.user.to_string() user_id = sync_result_builder.sync_config.user.to_string()
# 1. Start by fetching all ephemeral events in rooms we've joined (if required). # 1. Start by fetching all ephemeral events in rooms we've joined (if required).
if ( block_all_room_ephemeral = (
sync_result_builder.sync_config.filter_collection.blocks_all_room_ephemeral() sync_result_builder.sync_config.filter_collection.blocks_all_rooms()
): or sync_result_builder.sync_config.filter_collection.blocks_all_room_ephemeral()
)
if block_all_room_ephemeral:
ephemeral_by_room: Dict[str, List[JsonDict]] = {} ephemeral_by_room: Dict[str, List[JsonDict]] = {}
else: else:
now_token, ephemeral_by_room = await self.ephemeral_by_room( now_token, ephemeral_by_room = await self.ephemeral_by_room(
@ -1870,7 +1890,7 @@ class SyncHandler:
) )
if not tags_by_room: if not tags_by_room:
logger.debug("no-oping sync") logger.debug("no-oping sync")
return set(), set(), set(), set() return set(), set()
# 3. Work out which rooms need reporting in the sync response. # 3. Work out which rooms need reporting in the sync response.
ignored_users = await self.store.ignored_users(user_id) ignored_users = await self.store.ignored_users(user_id)
@ -1899,6 +1919,7 @@ class SyncHandler:
# joined or archived). # joined or archived).
async def handle_room_entries(room_entry: "RoomSyncResultBuilder") -> None: async def handle_room_entries(room_entry: "RoomSyncResultBuilder") -> None:
logger.debug("Generating room entry for %s", room_entry.room_id) logger.debug("Generating room entry for %s", room_entry.room_id)
# Note that this mutates sync_result_builder.{joined,archived}.
await self._generate_room_entry( await self._generate_room_entry(
sync_result_builder, sync_result_builder,
room_entry, room_entry,
@ -1915,20 +1936,7 @@ class SyncHandler:
sync_result_builder.invited.extend(invited) sync_result_builder.invited.extend(invited)
sync_result_builder.knocked.extend(knocked) sync_result_builder.knocked.extend(knocked)
# 5. Work out which users have joined or left rooms we're in. We use this return set(newly_joined_rooms), set(newly_left_rooms)
# to build the device_list part of the sync response in
# `_generate_sync_entry_for_device_list`.
(
newly_joined_or_invited_or_knocked_users,
newly_left_users,
) = sync_result_builder.calculate_user_changes()
return (
set(newly_joined_rooms),
newly_joined_or_invited_or_knocked_users,
set(newly_left_rooms),
newly_left_users,
)
async def _have_rooms_changed( async def _have_rooms_changed(
self, sync_result_builder: "SyncResultBuilder" self, sync_result_builder: "SyncResultBuilder"