Speed up get_unread_event_push_actions_by_room (#13005)

Fixes #11887 hopefully.

The core change here is that `event_push_summary` now holds a summary of counts up until a much more recent point, meaning that the range of rows we need to count in `event_push_actions` is much smaller.

This needs two major changes:
1. When we get a receipt we need to recalculate `event_push_summary` rather than just delete it
2. The logic for deleting `event_push_actions` is now divorced from calculating `event_push_summary`.

In future it would be good to calculate `event_push_summary` while we persist a new event (it should just be a case of adding one to the relevant rows in `event_push_summary`), as that will further simplify the get counts logic and remove the need for us to periodically update `event_push_summary` in a background job.
This commit is contained in:
Erik Johnston 2022-06-15 16:17:14 +01:00 committed by GitHub
parent 9ad2197fa7
commit 0d1d3e0708
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 322 additions and 151 deletions

View file

@ -118,7 +118,7 @@ class ReceiptsWorkerStore(SQLBaseStore):
return self._receipts_id_gen.get_current_token()
async def get_last_receipt_event_id_for_user(
self, user_id: str, room_id: str, receipt_types: Iterable[str]
self, user_id: str, room_id: str, receipt_types: Collection[str]
) -> Optional[str]:
"""
Fetch the event ID for the latest receipt in a room with one of the given receipt types.
@ -126,58 +126,63 @@ class ReceiptsWorkerStore(SQLBaseStore):
Args:
user_id: The user to fetch receipts for.
room_id: The room ID to fetch the receipt for.
receipt_type: The receipt types to fetch. Earlier receipt types
are given priority if multiple receipts point to the same event.
receipt_type: The receipt types to fetch.
Returns:
The latest receipt, if one exists.
"""
latest_event_id: Optional[str] = None
latest_stream_ordering = 0
for receipt_type in receipt_types:
result = await self._get_last_receipt_event_id_for_user(
user_id, room_id, receipt_type
)
if result is None:
continue
event_id, stream_ordering = result
result = await self.db_pool.runInteraction(
"get_last_receipt_event_id_for_user",
self.get_last_receipt_for_user_txn,
user_id,
room_id,
receipt_types,
)
if not result:
return None
if latest_event_id is None or latest_stream_ordering < stream_ordering:
latest_event_id = event_id
latest_stream_ordering = stream_ordering
event_id, _ = result
return event_id
return latest_event_id
@cached()
async def _get_last_receipt_event_id_for_user(
self, user_id: str, room_id: str, receipt_type: str
def get_last_receipt_for_user_txn(
self,
txn: LoggingTransaction,
user_id: str,
room_id: str,
receipt_types: Collection[str],
) -> Optional[Tuple[str, int]]:
"""
Fetch the event ID and stream ordering for the latest receipt.
Fetch the event ID and stream_ordering for the latest receipt in a room
with one of the given receipt types.
Args:
user_id: The user to fetch receipts for.
room_id: The room ID to fetch the receipt for.
receipt_type: The receipt type to fetch.
receipt_type: The receipt types to fetch.
Returns:
The event ID and stream ordering of the latest receipt, if one exists;
otherwise `None`.
The latest receipt, if one exists.
"""
sql = """
clause, args = make_in_list_sql_clause(
self.database_engine, "receipt_type", receipt_types
)
sql = f"""
SELECT event_id, stream_ordering
FROM receipts_linearized
INNER JOIN events USING (room_id, event_id)
WHERE user_id = ?
WHERE {clause}
AND user_id = ?
AND room_id = ?
AND receipt_type = ?
ORDER BY stream_ordering DESC
LIMIT 1
"""
def f(txn: LoggingTransaction) -> Optional[Tuple[str, int]]:
txn.execute(sql, (user_id, room_id, receipt_type))
return cast(Optional[Tuple[str, int]], txn.fetchone())
args.extend((user_id, room_id))
txn.execute(sql, args)
return await self.db_pool.runInteraction("get_own_receipt_for_user", f)
return cast(Optional[Tuple[str, int]], txn.fetchone())
async def get_receipts_for_user(
self, user_id: str, receipt_types: Iterable[str]
@ -577,8 +582,11 @@ class ReceiptsWorkerStore(SQLBaseStore):
) -> None:
self._get_receipts_for_user_with_orderings.invalidate((user_id, receipt_type))
self._get_linearized_receipts_for_room.invalidate((room_id,))
self._get_last_receipt_event_id_for_user.invalidate(
(user_id, room_id, receipt_type)
# We use this method to invalidate so that we don't end up with circular
# dependencies between the receipts and push action stores.
self._attempt_to_invalidate_cache(
"get_unread_event_push_actions_by_room_for_user", (room_id,)
)
def process_replication_rows(