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

Skip unused calculations in sync handler #14908

Merged
merged 19 commits into from
Feb 2, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/14908.misc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Improve performance of `/sync` in a few situations.
3 changes: 3 additions & 0 deletions synapse/api/filtering.py
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,9 @@ async def filter_room_account_data(
await self._room_filter.filter(events)
)

def blocks_all_rooms(self) -> bool:
return self._room_filter.filters_all_rooms()

DMRobertson marked this conversation as resolved.
Show resolved Hide resolved
def blocks_all_presence(self) -> bool:
return (
self._presence_filter.filters_all_types()
Expand Down
213 changes: 120 additions & 93 deletions synapse/handlers/sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -1440,18 +1440,22 @@ async def generate_sync_result(

logger.debug("Fetching room data")

block_all_presence_data = (
since_token is None and sync_config.filter_collection.blocks_all_presence()
)
DMRobertson marked this conversation as resolved.
Show resolved Hide resolved
include_device_list_updates = bool(since_token and since_token.device_list_key)
(
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
sync_result_builder,
account_data_by_room,
block_all_presence_data,
include_device_list_updates,
)

block_all_presence_data = (
since_token is None and sync_config.filter_collection.blocks_all_presence()
)
if self.hs_config.server.use_presence and not block_all_presence_data:
logger.debug("Fetching presence data")
await self._generate_sync_entry_for_presence(
Expand All @@ -1463,13 +1467,16 @@ async def generate_sync_result(
logger.debug("Fetching to-device data")
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,
)
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,
)
else:
device_lists = DeviceListUpdates()

logger.debug("Fetching OTK data")
device_id = sync_config.device_id
Expand Down Expand Up @@ -1539,99 +1546,93 @@ async def _generate_sync_entry_for_device_list(

user_id = sync_result_builder.sync_config.user.to_string()
since_token = sync_result_builder.since_token
assert since_token is not None

# 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
)
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
# device keys for, and which users we aren't going to track changes
# for anymore.
#
# For the first step we check:
# a. if any users we share a room with have updated their devices,
# and
# b. we also check if we've joined any new rooms, or if a user has
# joined a room we're in.
#
# 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
# that were in a room we've left.

users_that_have_changed = set()
# 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
# for anymore.
#
# For the first step we check:
# a. if any users we share a room with have updated their devices,
# and
# b. we also check if we've joined any new rooms, or if a user has
# joined a room we're in.
#
# 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
# that were in a room we've left.

joined_rooms = sync_result_builder.joined_room_ids
users_that_have_changed = set()

# Step 1a, check for changes in devices of users we share a room
# with
#
# 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
# the last sync then it's likely more efficient to compare the rooms
# 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
# share a room with our user and check if those users have changed.
cache_result = self.store.get_cached_device_list_changes(
since_token.device_list_key
)
if cache_result.hit:
changed_users = cache_result.entities

result = await self.store.get_rooms_for_users(changed_users)

for changed_user_id, entries in result.items():
# Check if the changed user shares any rooms with the user,
# or if the changed user is the syncing user (as we always
# want to include device list updates of their own devices).
if user_id == changed_user_id or any(
rid in joined_rooms for rid in entries
):
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,
)
)
joined_rooms = sync_result_builder.joined_room_ids

# 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)
# Step 1a, check for changes in devices of users we share a room
# with
#
# 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
# the last sync then it's likely more efficient to compare the rooms
# 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
# share a room with our user and check if those users have changed.
cache_result = self.store.get_cached_device_list_changes(
since_token.device_list_key
)
if cache_result.hit:
changed_users = cache_result.entities

# 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)
result = await self.store.get_rooms_for_users(changed_users)

user_signatures_changed = (
await self.store.get_users_whose_signatures_changed(
user_id, since_token.device_list_key
for changed_user_id, entries in result.items():
# Check if the changed user shares any rooms with the user,
# or if the changed user is the syncing user (as we always
# want to include device list updates of their own devices).
if user_id == changed_user_id or any(
rid in joined_rooms for rid in entries
):
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,
)
)
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)
# 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)

# 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)
# 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)

return DeviceListUpdates(
changed=users_that_have_changed, left=newly_left_users
)
else:
return DeviceListUpdates()
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
async def _generate_sync_entry_for_to_device(
Expand Down Expand Up @@ -1708,6 +1709,7 @@ async def _generate_sync_entry_for_account_data(
since_token = sync_result_builder.since_token

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,
account_data_by_room,
Expand All @@ -1724,6 +1726,7 @@ async def _generate_sync_entry_for_account_data(
sync_config.user
)
else:
# TODO Do not fetch room account data if it will be unused.
(
global_account_data,
account_data_by_room,
Expand Down Expand Up @@ -1806,6 +1809,8 @@ async def _generate_sync_entry_for_rooms(
self,
sync_result_builder: "SyncResultBuilder",
account_data_by_room: Dict[str, Dict[str, JsonDict]],
block_all_presence_data: bool,
include_device_list_updates: bool,
) -> Tuple[AbstractSet[str], AbstractSet[str], AbstractSet[str], AbstractSet[str]]:
"""Generates the rooms portion of the sync response. Populates the
`sync_result_builder` with the result.
Expand All @@ -1817,6 +1822,8 @@ async def _generate_sync_entry_for_rooms(
Args:
sync_result_builder
account_data_by_room: Dictionary of per room account data
block_all_presence_data: True if presence data will not be returned.
include_device_list_updates: True if device list updates will be returned.

Returns:
Returns a 4-tuple describing rooms the user has joined or left, and users who've
Expand All @@ -1832,10 +1839,25 @@ async def _generate_sync_entry_for_rooms(

since_token = sync_result_builder.since_token

# If all rooms are blocked, we can skip bits of processing.
block_all_rooms = (
sync_result_builder.sync_config.filter_collection.blocks_all_rooms()
)

# 0. If there are no rooms to return *and* we don't care about presence
# or device list updates, there's nothing to do.
if (
block_all_rooms
and block_all_presence_data
and not include_device_list_updates
):
return set(), set(), set(), set()

DMRobertson marked this conversation as resolved.
Show resolved Hide resolved
# 1. Start by fetching all ephemeral events in rooms we've joined (if required).
user_id = sync_result_builder.sync_config.user.to_string()
block_all_room_ephemeral = (
since_token is None
block_all_rooms
or since_token is None
and sync_result_builder.sync_config.filter_collection.blocks_all_room_ephemeral()
clokep marked this conversation as resolved.
Show resolved Hide resolved
)

Expand Down Expand Up @@ -1890,6 +1912,7 @@ async def _generate_sync_entry_for_rooms(
# joined or archived).
async def handle_room_entries(room_entry: "RoomSyncResultBuilder") -> None:
logger.debug("Generating room entry for %s", room_entry.room_id)
# Note that this mutates sync_result_builder.{joined,archived}.
DMRobertson marked this conversation as resolved.
Show resolved Hide resolved
await self._generate_room_entry(
sync_result_builder,
room_entry,
Expand All @@ -1907,12 +1930,16 @@ async def handle_room_entries(room_entry: "RoomSyncResultBuilder") -> None:
sync_result_builder.knocked.extend(knocked)

# 5. Work out which users have joined or left rooms we're in. We use this
# to build the device_list part of the sync response in
# to build the presence and device_list parts of the sync response in
# `_generate_sync_entry_for_device_list`.
clokep marked this conversation as resolved.
Show resolved Hide resolved
(
newly_joined_or_invited_or_knocked_users,
newly_left_users,
) = sync_result_builder.calculate_user_changes()
if not block_all_presence_data or include_device_list_updates:
(
newly_joined_or_invited_or_knocked_users,
newly_left_users,
) = sync_result_builder.calculate_user_changes()
else:
newly_joined_or_invited_or_knocked_users = set()
newly_left_users = set()

return (
set(newly_joined_rooms),
Expand Down