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

Cache the result of fetching the room hierarchy over federation. #10647

Merged
merged 6 commits into from
Aug 26, 2021
Merged
Show file tree
Hide file tree
Changes from 4 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/10647.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Improve the performance of the `/hiearchy` API (from [MSC2946](https://github.com/matrix-org/matrix-doc/pull/2946)) by caching responses received over federation.
clokep marked this conversation as resolved.
Show resolved Hide resolved
106 changes: 66 additions & 40 deletions synapse/federation/federation_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,23 @@ def __init__(self, hs: "HomeServer"):
reset_expiry_on_get=False,
)

# A cache for fetching the room hierarchy over federation.
#
# Some stale data over federation is OK, but must be refreshed
# periodically since the local server is in the room.
#
# It is a map of (room ID, suggested-only) -> the response of
# get_room_hierarchy.
self._get_room_hierarchy_cache: ExpiringCache[
Tuple[str, bool], Tuple[JsonDict, Sequence[JsonDict], Sequence[str]]
] = ExpiringCache(
cache_name="get_room_hierarchy_cache",
clock=self._clock,
max_len=1000,
expiry_ms=5 * 60 * 1000,
reset_expiry_on_get=False,
)

def _clear_tried_cache(self):
"""Clear pdu_destination_tried cache"""
now = self._clock.time_msec()
Expand Down Expand Up @@ -1324,6 +1341,10 @@ async def get_room_hierarchy(
remote servers
"""

cached_result = self._get_room_hierarchy_cache.get((room_id, suggested_only))
if cached_result:
return cached_result

async def send_request(
destination: str,
) -> Tuple[JsonDict, Sequence[JsonDict], Sequence[str]]:
Expand Down Expand Up @@ -1370,58 +1391,63 @@ async def send_request(
return room, children, inaccessible_children

try:
return await self._try_destination_list(
result = await self._try_destination_list(
"fetch room hierarchy",
destinations,
send_request,
failover_on_unknown_endpoint=True,
)
except SynapseError as e:
# If an unexpected error occurred, re-raise it.
if e.code != 502:
raise

# Fallback to the old federation API and translate the results if
# no servers implement the new API.
#
# The algorithm below is a bit inefficient as it only attempts to
# get information for the requested room, but the legacy API may
# parse information for the requested room, but the legacy API may
# return additional layers.
if e.code == 502:
legacy_result = await self.get_space_summary(
destinations,
room_id,
suggested_only,
max_rooms_per_space=None,
exclude_rooms=[],
)
legacy_result = await self.get_space_summary(
destinations,
room_id,
suggested_only,
max_rooms_per_space=None,
exclude_rooms=[],
)

# Find the requested room in the response (and remove it).
for _i, room in enumerate(legacy_result.rooms):
if room.get("room_id") == room_id:
break
else:
# The requested room was not returned, nothing we can do.
raise
requested_room = legacy_result.rooms.pop(_i)

# Find any children events of the requested room.
children_events = []
children_room_ids = set()
for event in legacy_result.events:
if event.room_id == room_id:
children_events.append(event.data)
children_room_ids.add(event.state_key)
# And add them under the requested room.
requested_room["children_state"] = children_events

# Find the children rooms.
children = []
for room in legacy_result.rooms:
if room.get("room_id") in children_room_ids:
children.append(room)

# It isn't clear from the response whether some of the rooms are
# not accessible.
return requested_room, children, ()

raise
# Find the requested room in the response (and remove it).
for _i, room in enumerate(legacy_result.rooms):
if room.get("room_id") == room_id:
break
else:
# The requested room was not returned, nothing we can do.
raise
requested_room = legacy_result.rooms.pop(_i)

# Find any children events of the requested room.
children_events = []
children_room_ids = set()
for event in legacy_result.events:
if event.room_id == room_id:
children_events.append(event.data)
children_room_ids.add(event.state_key)
# And add them under the requested room.
requested_room["children_state"] = children_events

# Find the children rooms.
children = []
for room in legacy_result.rooms:
if room.get("room_id") in children_room_ids:
children.append(room)

# It isn't clear from the response whether some of the rooms are
# not accessible.
result = (requested_room, children, ())

# Cache the result to avoid fetching data over federation every time.
self._get_room_hierarchy_cache[(room_id, suggested_only)] = result
return result


@attr.s(frozen=True, slots=True, auto_attribs=True)
Expand Down