Fetch a room's hierarchy caches concurrently on the cold path

The cached fast path serialised its per-room lookups
(is_partial_state_room, then the hierarchy state cache fill, then room
version and stats), which undid the traversal-level prefetch parallelism
on a cold cache under latency. Gather them concurrently instead, warming
the room-stats and room-version caches that the rest of the path reads.

Since the partial-state check now races the cache fill, drop the cached
hierarchy state again when the room turns out to be partial-stated, and
register get_room_hierarchy_state in _invalidate_state_caches(_all) so
state resets and un-partial-stating invalidate it too.
This commit is contained in:
Matthew Hodgson
2026-08-08 21:56:58 +03:00
parent c159933058
commit feeb3992ee
2 changed files with 38 additions and 4 deletions
+36 -4
View File
@@ -50,7 +50,8 @@ from synapse.logging.context import make_deferred_yieldable, run_in_background
from synapse.storage.databases.main.state import RoomHierarchyState
from synapse.types import JsonDict, JsonMapping, Requester, StateMap, StrCollection
from synapse.types.state import StateFilter
from synapse.util.async_helpers import yieldable_gather_results
from synapse.util import unwrapFirstError
from synapse.util.async_helpers import gather_results, yieldable_gather_results
from synapse.util.caches.response_cache import ResponseCache
if TYPE_CHECKING:
@@ -581,15 +582,46 @@ class RoomSummaryHandler:
Returns:
A room entry if the room should be returned. None, otherwise.
"""
# Fast path: serve the entry entirely from the cached room hierarchy
# state + room stats (both invalidated when the underlying data
# changes), so a warm request does no DB work at all. Partial-state
# rooms fall back to the uncached path, as their cached current state
# may be incomplete; unknown rooms fall back for the pending-invite
# handling.
hierarchy_state = None
if not await self._store.is_partial_state_room(room_id):
hierarchy_state = await self._store.get_room_hierarchy_state(room_id)
#
# The per-room lookups are independent, so run them (including warming
# the room-stats / room-version caches read later in this path)
# concurrently: on a cold cache a serial chain here would otherwise
# undo the traversal-level parallelism on latency-bound deployments.
async def prime_room_version() -> None:
try:
await self._store.get_room_version(room_id)
except (NotFoundError, UnsupportedRoomVersionError):
pass
is_partial_state, hierarchy_state, _, _ = await make_deferred_yieldable(
gather_results(
(
# NB lambdas as run_in_background's typing rejects @cached methods.
run_in_background(
lambda: self._store.is_partial_state_room(room_id)
),
run_in_background(
lambda: self._store.get_room_hierarchy_state(room_id)
),
run_in_background(lambda: self._store.get_room_with_stats(room_id)),
run_in_background(prime_room_version),
),
consumeErrors=True,
)
).addErrback(unwrapFirstError)
if is_partial_state:
# The value cached just now may have been computed from partial
# state: drop it and use the uncached path (which resolves state
# via the storage controllers).
self._store.get_room_hierarchy_state.invalidate((room_id,))
hierarchy_state = None
if hierarchy_state is None:
return await self._summarize_local_room_uncached(
requester,
+2
View File
@@ -139,6 +139,7 @@ class SQLBaseStore(metaclass=ABCMeta):
self._attempt_to_invalidate_cache("get_partial_current_state_ids", (room_id,))
self._attempt_to_invalidate_cache("get_room_type", (room_id,))
self._attempt_to_invalidate_cache("get_room_encryption", (room_id,))
self._attempt_to_invalidate_cache("get_room_hierarchy_state", (room_id,))
self._attempt_to_invalidate_cache(
"get_sliding_sync_rooms_for_user_from_membership_snapshots", None
)
@@ -171,6 +172,7 @@ class SQLBaseStore(metaclass=ABCMeta):
self._attempt_to_invalidate_cache("get_room_summary", (room_id,))
self._attempt_to_invalidate_cache("get_room_type", (room_id,))
self._attempt_to_invalidate_cache("get_room_encryption", (room_id,))
self._attempt_to_invalidate_cache("get_room_hierarchy_state", (room_id,))
self._attempt_to_invalidate_cache(
"get_sliding_sync_rooms_for_user_from_membership_snapshots", None
)