@@ -233,14 +233,30 @@ def _get_unread_counts_by_pos_txn(
233233
234234 counts = NotifCounts ()
235235
236- # First we pull the counts from the summary table
236+ # First we pull the counts from the summary table.
237+ #
238+ # We check that `last_receipt_stream_ordering` matches the stream
239+ # ordering given. If it doesn't match then a new read receipt has arrived and
240+ # we haven't yet updated the counts in `event_push_summary` to reflect
241+ # that; in that case we simply ignore `event_push_summary` counts
242+ # and do a manual count of all of the rows in the `event_push_actions` table
243+ # for this user/room.
244+ #
245+ # If `last_receipt_stream_ordering` is null then that means it's up to
246+ # date (as the row was written by an older version of Synapse that
247+ # updated `event_push_summary` synchronously when persisting a new read
248+ # receipt).
237249 txn .execute (
238250 """
239251 SELECT stream_ordering, notif_count, COALESCE(unread_count, 0)
240252 FROM event_push_summary
241- WHERE room_id = ? AND user_id = ? AND stream_ordering > ?
253+ WHERE room_id = ? AND user_id = ?
254+ AND (
255+ (last_receipt_stream_ordering IS NULL AND stream_ordering > ?)
256+ OR last_receipt_stream_ordering = ?
257+ )
242258 """ ,
243- (room_id , user_id , stream_ordering ),
259+ (room_id , user_id , stream_ordering , stream_ordering ),
244260 )
245261 row = txn .fetchone ()
246262
@@ -263,9 +279,9 @@ def _get_unread_counts_by_pos_txn(
263279 if row :
264280 counts .highlight_count += row [0 ]
265281
266- # Finally we need to count push actions that haven 't been summarized
267- # yet.
268- # We only want to pull out push actions that we haven't summarized yet .
282+ # Finally we need to count push actions that aren 't included in the
283+ # summary returned above, e.g. recent events that haven't been
284+ # summarized yet, or the summary is empty due to a recent read receipt .
269285 stream_ordering = max (stream_ordering , summary_stream_ordering )
270286 notify_count , unread_count = self ._get_notif_unread_count_for_user_room (
271287 txn , room_id , user_id , stream_ordering
@@ -800,6 +816,19 @@ async def _rotate_notifs(self) -> None:
800816 self ._doing_notif_rotation = True
801817
802818 try :
819+ # First we recalculate push summaries and delete stale push actions
820+ # for rooms/users with new receipts.
821+ while True :
822+ logger .debug ("Handling new receipts" )
823+
824+ caught_up = await self .db_pool .runInteraction (
825+ "_handle_new_receipts_for_notifs_txn" ,
826+ self ._handle_new_receipts_for_notifs_txn ,
827+ )
828+ if caught_up :
829+ break
830+
831+ # Then we update the event push summaries for any new events
803832 while True :
804833 logger .info ("Rotating notifications" )
805834
@@ -810,10 +839,110 @@ async def _rotate_notifs(self) -> None:
810839 break
811840 await self .hs .get_clock ().sleep (self ._rotate_delay )
812841
842+ # Finally we clear out old event push actions.
813843 await self ._remove_old_push_actions_that_have_rotated ()
814844 finally :
815845 self ._doing_notif_rotation = False
816846
847+ def _handle_new_receipts_for_notifs_txn (self , txn : LoggingTransaction ) -> bool :
848+ """Check for new read receipts and delete from event push actions.
849+
850+ Any push actions which predate the user's most recent read receipt are
851+ now redundant, so we can remove them from `event_push_actions` and
852+ update `event_push_summary`.
853+ """
854+
855+ limit = 100
856+
857+ min_stream_id = self .db_pool .simple_select_one_onecol_txn (
858+ txn ,
859+ table = "event_push_summary_last_receipt_stream_id" ,
860+ keyvalues = {},
861+ retcol = "stream_id" ,
862+ )
863+
864+ sql = """
865+ SELECT r.stream_id, r.room_id, r.user_id, e.stream_ordering
866+ FROM receipts_linearized AS r
867+ INNER JOIN events AS e USING (event_id)
868+ WHERE r.stream_id > ? AND user_id LIKE ?
869+ ORDER BY r.stream_id ASC
870+ LIMIT ?
871+ """
872+
873+ # We only want local users, so we add a dodgy filter to the above query
874+ # and recheck it below.
875+ user_filter = "%:" + self .hs .hostname
876+
877+ txn .execute (
878+ sql ,
879+ (
880+ min_stream_id ,
881+ user_filter ,
882+ limit ,
883+ ),
884+ )
885+ rows = txn .fetchall ()
886+
887+ # For each new read receipt we delete push actions from before it and
888+ # recalculate the summary.
889+ for _ , room_id , user_id , stream_ordering in rows :
890+ # Only handle our own read receipts.
891+ if not self .hs .is_mine_id (user_id ):
892+ continue
893+
894+ txn .execute (
895+ """
896+ DELETE FROM event_push_actions
897+ WHERE room_id = ?
898+ AND user_id = ?
899+ AND stream_ordering <= ?
900+ AND highlight = 0
901+ """ ,
902+ (room_id , user_id , stream_ordering ),
903+ )
904+
905+ old_rotate_stream_ordering = self .db_pool .simple_select_one_onecol_txn (
906+ txn ,
907+ table = "event_push_summary_stream_ordering" ,
908+ keyvalues = {},
909+ retcol = "stream_ordering" ,
910+ )
911+
912+ notif_count , unread_count = self ._get_notif_unread_count_for_user_room (
913+ txn , room_id , user_id , stream_ordering , old_rotate_stream_ordering
914+ )
915+
916+ self .db_pool .simple_upsert_txn (
917+ txn ,
918+ table = "event_push_summary" ,
919+ keyvalues = {"room_id" : room_id , "user_id" : user_id },
920+ values = {
921+ "notif_count" : notif_count ,
922+ "unread_count" : unread_count ,
923+ "stream_ordering" : old_rotate_stream_ordering ,
924+ "last_receipt_stream_ordering" : stream_ordering ,
925+ },
926+ )
927+
928+ # We always update `event_push_summary_last_receipt_stream_id` to
929+ # ensure that we don't rescan the same receipts for remote users.
930+ #
931+ # This requires repeatable read to be safe, as we need the
932+ # `MAX(stream_id)` to not include any new rows that have been committed
933+ # since the start of the transaction (since those rows won't have been
934+ # returned by the query above). Alternatively we could query the max
935+ # stream ID at the start of the transaction and bound everything by
936+ # that.
937+ txn .execute (
938+ """
939+ UPDATE event_push_summary_last_receipt_stream_id
940+ SET stream_id = (SELECT COALESCE(MAX(stream_id), 0) FROM receipts_linearized)
941+ """
942+ )
943+
944+ return len (rows ) < limit
945+
817946 def _rotate_notifs_txn (self , txn : LoggingTransaction ) -> bool :
818947 """Archives older notifications into event_push_summary. Returns whether
819948 the archiving process has caught up or not.
@@ -1033,66 +1162,6 @@ def remove_old_push_actions_that_have_rotated_txn(
10331162 if done :
10341163 break
10351164
1036- def _remove_old_push_actions_before_txn (
1037- self , txn : LoggingTransaction , room_id : str , user_id : str , stream_ordering : int
1038- ) -> None :
1039- """
1040- Purges old push actions for a user and room before a given
1041- stream_ordering.
1042-
1043- We however keep a months worth of highlighted notifications, so that
1044- users can still get a list of recent highlights.
1045-
1046- Args:
1047- txn: The transaction
1048- room_id: Room ID to delete from
1049- user_id: user ID to delete for
1050- stream_ordering: The lowest stream ordering which will
1051- not be deleted.
1052- """
1053- txn .call_after (
1054- self .get_unread_event_push_actions_by_room_for_user .invalidate ,
1055- (room_id , user_id ),
1056- )
1057-
1058- # We need to join on the events table to get the received_ts for
1059- # event_push_actions and sqlite won't let us use a join in a delete so
1060- # we can't just delete where received_ts < x. Furthermore we can
1061- # only identify event_push_actions by a tuple of room_id, event_id
1062- # we we can't use a subquery.
1063- # Instead, we look up the stream ordering for the last event in that
1064- # room received before the threshold time and delete event_push_actions
1065- # in the room with a stream_odering before that.
1066- txn .execute (
1067- "DELETE FROM event_push_actions "
1068- " WHERE user_id = ? AND room_id = ? AND "
1069- " stream_ordering <= ?"
1070- " AND ((stream_ordering < ? AND highlight = 1) or highlight = 0)" ,
1071- (user_id , room_id , stream_ordering , self .stream_ordering_month_ago ),
1072- )
1073-
1074- old_rotate_stream_ordering = self .db_pool .simple_select_one_onecol_txn (
1075- txn ,
1076- table = "event_push_summary_stream_ordering" ,
1077- keyvalues = {},
1078- retcol = "stream_ordering" ,
1079- )
1080-
1081- notif_count , unread_count = self ._get_notif_unread_count_for_user_room (
1082- txn , room_id , user_id , stream_ordering , old_rotate_stream_ordering
1083- )
1084-
1085- self .db_pool .simple_upsert_txn (
1086- txn ,
1087- table = "event_push_summary" ,
1088- keyvalues = {"room_id" : room_id , "user_id" : user_id },
1089- values = {
1090- "notif_count" : notif_count ,
1091- "unread_count" : unread_count ,
1092- "stream_ordering" : old_rotate_stream_ordering ,
1093- },
1094- )
1095-
10961165
10971166class EventPushActionsStore (EventPushActionsWorkerStore ):
10981167 EPA_HIGHLIGHT_INDEX = "epa_highlight_index"
0 commit comments