Optimise get_rooms_for_user (drop with_stream_ordering) (#13787)

This commit is contained in:
Nick Mills-Barrett 2022-09-29 14:55:12 +01:00 committed by GitHub
parent be76cd8200
commit a466164647
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 66 additions and 75 deletions

1
changelog.d/13787.misc Normal file
View file

@ -0,0 +1 @@
Optimise get rooms for user calls. Contributed by Nick @ Beeper (@fizzadar).

View file

@ -273,11 +273,9 @@ class DeviceWorkerHandler:
possibly_left = possibly_changed | possibly_left possibly_left = possibly_changed | possibly_left
# Double check if we still share rooms with the given user. # Double check if we still share rooms with the given user.
users_rooms = await self.store.get_rooms_for_users_with_stream_ordering( users_rooms = await self.store.get_rooms_for_users(possibly_left)
possibly_left
)
for changed_user_id, entries in users_rooms.items(): for changed_user_id, entries in users_rooms.items():
if any(e.room_id in room_ids for e in entries): if any(rid in room_ids for rid in entries):
possibly_left.discard(changed_user_id) possibly_left.discard(changed_user_id)
else: else:
possibly_joined.discard(changed_user_id) possibly_joined.discard(changed_user_id)

View file

@ -1490,16 +1490,14 @@ class SyncHandler:
since_token.device_list_key since_token.device_list_key
) )
if changed_users is not None: if changed_users is not None:
result = await self.store.get_rooms_for_users_with_stream_ordering( result = await self.store.get_rooms_for_users(changed_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(
e.room_id in joined_rooms for e 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: else:
@ -1533,13 +1531,9 @@ class SyncHandler:
newly_left_users.update(left_users) newly_left_users.update(left_users)
# Remove any users that we still share a room with. # Remove any users that we still share a room with.
left_users_rooms = ( left_users_rooms = await self.store.get_rooms_for_users(newly_left_users)
await self.store.get_rooms_for_users_with_stream_ordering(
newly_left_users
)
)
for user_id, entries in left_users_rooms.items(): for user_id, entries in left_users_rooms.items():
if any(e.room_id in joined_rooms for e in entries): if any(rid in joined_rooms for rid in entries):
newly_left_users.discard(user_id) newly_left_users.discard(user_id)
return DeviceListUpdates( return DeviceListUpdates(

View file

@ -94,6 +94,7 @@ class SQLBaseStore(metaclass=ABCMeta):
self._attempt_to_invalidate_cache( self._attempt_to_invalidate_cache(
"get_rooms_for_user_with_stream_ordering", (user_id,) "get_rooms_for_user_with_stream_ordering", (user_id,)
) )
self._attempt_to_invalidate_cache("get_rooms_for_user", (user_id,))
# Purge other caches based on room state. # Purge other caches based on room state.
self._attempt_to_invalidate_cache("get_room_summary", (room_id,)) self._attempt_to_invalidate_cache("get_room_summary", (room_id,))

View file

@ -205,6 +205,7 @@ class CacheInvalidationWorkerStore(SQLBaseStore):
self.get_rooms_for_user_with_stream_ordering.invalidate( self.get_rooms_for_user_with_stream_ordering.invalidate(
(data.state_key,) (data.state_key,)
) )
self.get_rooms_for_user.invalidate((data.state_key,))
else: else:
raise Exception("Unknown events stream row type %s" % (row.type,)) raise Exception("Unknown events stream row type %s" % (row.type,))

View file

@ -15,7 +15,6 @@
import logging import logging
from typing import ( from typing import (
TYPE_CHECKING, TYPE_CHECKING,
Callable,
Collection, Collection,
Dict, Dict,
FrozenSet, FrozenSet,
@ -52,7 +51,6 @@ from synapse.types import JsonDict, PersistedEventPosition, StateMap, get_domain
from synapse.util.async_helpers import Linearizer from synapse.util.async_helpers import Linearizer
from synapse.util.caches import intern_string from synapse.util.caches import intern_string
from synapse.util.caches.descriptors import _CacheContext, cached, cachedList from synapse.util.caches.descriptors import _CacheContext, cached, cachedList
from synapse.util.cancellation import cancellable
from synapse.util.iterutils import batch_iter from synapse.util.iterutils import batch_iter
from synapse.util.metrics import Measure from synapse.util.metrics import Measure
@ -600,58 +598,6 @@ class RoomMemberWorkerStore(EventsWorkerStore):
for room_id, instance, stream_id in txn for room_id, instance, stream_id in txn
) )
@cachedList(
cached_method_name="get_rooms_for_user_with_stream_ordering",
list_name="user_ids",
)
async def get_rooms_for_users_with_stream_ordering(
self, user_ids: Collection[str]
) -> Dict[str, FrozenSet[GetRoomsForUserWithStreamOrdering]]:
"""A batched version of `get_rooms_for_user_with_stream_ordering`.
Returns:
Map from user_id to set of rooms that is currently in.
"""
return await self.db_pool.runInteraction(
"get_rooms_for_users_with_stream_ordering",
self._get_rooms_for_users_with_stream_ordering_txn,
user_ids,
)
def _get_rooms_for_users_with_stream_ordering_txn(
self, txn: LoggingTransaction, user_ids: Collection[str]
) -> Dict[str, FrozenSet[GetRoomsForUserWithStreamOrdering]]:
clause, args = make_in_list_sql_clause(
self.database_engine,
"c.state_key",
user_ids,
)
sql = f"""
SELECT c.state_key, room_id, e.instance_name, e.stream_ordering
FROM current_state_events AS c
INNER JOIN events AS e USING (room_id, event_id)
WHERE
c.type = 'm.room.member'
AND c.membership = ?
AND {clause}
"""
txn.execute(sql, [Membership.JOIN] + args)
result: Dict[str, Set[GetRoomsForUserWithStreamOrdering]] = {
user_id: set() for user_id in user_ids
}
for user_id, room_id, instance, stream_id in txn:
result[user_id].add(
GetRoomsForUserWithStreamOrdering(
room_id, PersistedEventPosition(instance, stream_id)
)
)
return {user_id: frozenset(v) for user_id, v in result.items()}
async def get_users_server_still_shares_room_with( async def get_users_server_still_shares_room_with(
self, user_ids: Collection[str] self, user_ids: Collection[str]
) -> Set[str]: ) -> Set[str]:
@ -693,19 +639,68 @@ class RoomMemberWorkerStore(EventsWorkerStore):
return {row[0] for row in txn} return {row[0] for row in txn}
@cancellable @cached(max_entries=500000, iterable=True)
async def get_rooms_for_user( async def get_rooms_for_user(self, user_id: str) -> FrozenSet[str]:
self, user_id: str, on_invalidate: Optional[Callable[[], None]] = None
) -> FrozenSet[str]:
"""Returns a set of room_ids the user is currently joined to. """Returns a set of room_ids the user is currently joined to.
If a remote user only returns rooms this server is currently If a remote user only returns rooms this server is currently
participating in. participating in.
""" """
rooms = await self.get_rooms_for_user_with_stream_ordering( rooms = self.get_rooms_for_user_with_stream_ordering.cache.get_immediate(
user_id, on_invalidate=on_invalidate (user_id,),
None,
update_metrics=False,
) )
return frozenset(r.room_id for r in rooms) if rooms:
return frozenset(r.room_id for r in rooms)
room_ids = await self.db_pool.simple_select_onecol(
table="current_state_events",
keyvalues={
"type": EventTypes.Member,
"membership": Membership.JOIN,
"state_key": user_id,
},
retcol="room_id",
desc="get_rooms_for_user",
)
return frozenset(room_ids)
@cachedList(
cached_method_name="get_rooms_for_user",
list_name="user_ids",
)
async def get_rooms_for_users(
self, user_ids: Collection[str]
) -> Dict[str, FrozenSet[str]]:
"""A batched version of `get_rooms_for_user`.
Returns:
Map from user_id to set of rooms that is currently in.
"""
rows = await self.db_pool.simple_select_many_batch(
table="current_state_events",
column="state_key",
iterable=user_ids,
retcols=(
"state_key",
"room_id",
),
keyvalues={
"type": EventTypes.Member,
"membership": Membership.JOIN,
},
desc="get_rooms_for_users",
)
user_rooms: Dict[str, Set[str]] = {user_id: set() for user_id in user_ids}
for row in rows:
user_rooms[row["state_key"]].add(row["room_id"])
return {key: frozenset(rooms) for key, rooms in user_rooms.items()}
@cached(max_entries=10000) @cached(max_entries=10000)
async def does_pair_of_users_share_a_room( async def does_pair_of_users_share_a_room(

View file

@ -159,6 +159,7 @@ class SyncTestCase(tests.unittest.HomeserverTestCase):
# Blow away caches (supported room versions can only change due to a restart). # Blow away caches (supported room versions can only change due to a restart).
self.store.get_rooms_for_user_with_stream_ordering.invalidate_all() self.store.get_rooms_for_user_with_stream_ordering.invalidate_all()
self.store.get_rooms_for_user.invalidate_all()
self.get_success(self.store._get_event_cache.clear()) self.get_success(self.store._get_event_cache.clear())
self.store._event_ref.clear() self.store._event_ref.clear()