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 all 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.misc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Improve the performance of the `/hierarchy` API (from [MSC2946](https://github.com/matrix-org/matrix-doc/pull/2946)) by caching responses received over federation.
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