Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

Track notification counts per thread (implement MSC3773) #13181

Closed
wants to merge 23 commits into from

Conversation

clokep
Copy link
Member

@clokep clokep commented Jul 5, 2022

Track notification counts on a per-thread basis, implementing MSC3773.

The overall design of this is to add a thread_id column to the event_push_actions (+ event_push_actions_staging) and event_push_summary tables. This allows the results to be segmented by the "main" timeline (which is represented by NULL in the database) and any other threads (which have the root event ID in the thread_id column).

When retrieving counts of notifications we can then segment based on the thread, this is opt-in for the client by providing a sync flag. In the case the client doesn't want separate threads we simply sum across all threads + the main timeline.

The summarization code also needs to be updated to be per thread, instead of just per room.

Please see MSC3773 for a description of the API changes.

Part of #12550

To Do

  • Update sync code to take into account the new flag.
  • Add an experimental config flag.
  • Write thread-specific tests.
  • Remove debug information.

@clokep clokep force-pushed the clokep/thread-notifs branch 6 times, most recently from 80d632a to 23a632e Compare July 6, 2022 17:26
@clokep clokep force-pushed the clokep/thread-notifs branch 2 times, most recently from b325dfc to 067563b Compare July 13, 2022 17:46
@clokep clokep force-pushed the clokep/thread-notifs branch 4 times, most recently from a6d67c2 to 9b92fce Compare August 3, 2022 14:46
@clokep clokep force-pushed the clokep/thread-notifs branch 2 times, most recently from 405e030 to 7d3e937 Compare August 4, 2022 19:51
synapse/api/filtering.py Outdated Show resolved Hide resolved
Comment on lines +400 to +403
# XXX All threads should have the same stream ordering?
max_summary_stream_ordering = max(
summary_stream_ordering, max_summary_stream_ordering
)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to figure out what's going on here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at my test server a bit, this isn't true (that all event_push_summary for a room/user have the same stream_ordering). I'm not really sure why I thought this, but it likely causes subtle bugs.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that stream ordering is the "max" stream ordering that we rotated from the event_push_actions table:

https://github.com/matrix-org/synapse/pull/13181/files#diff-f121377d76a7b35c60092da2c4bf8c849544459a2c676cf8e87057aff24ece54R1218

I think that means its equivalent to the stream ordering we rotated up to? They may differ but there should never be any relevant rows in EPA between the per-thread stream ordering and the max stream ordering?

Comment on lines +394 to +398
# TODO Delete zeroed out threads completely from the database.
elif notif_count or unread_count:
thread_counts[thread_id] = NotifCounts(
notify_count=notif_count, unread_count=unread_count
)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be worth looking at this again briefly not that some other bugs have been fixed.

@clokep
Copy link
Member Author

clokep commented Aug 31, 2022

Requesting review of this, but keeping it in draft. I think the implementation is far enough along that we could merge this, but don't want it merged accidentally.

@erikjohnston had a brief conversation of "how bad would it be to back this out if perf tanks". I think it wouldn't be too bad: backing out the code should essentially revert behavior, with the caveat there would be an additional unused column getting tossed around. This column could then be dropped without an issue.

@@ -181,6 +186,174 @@ def _mark_read(event_id: str) -> None:
_rotate()
_assert_counts(0, 0, 0)

def test_count_aggregation_threads(self) -> None:
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(I should possibly put this in a docstring...)

This is a "copy" of the test_count_aggregation test, but adapted to have both a "main" timeline and a thread in the same room.

@erikjohnston
Copy link
Member

(Oh, woops, I hit the merge develop branch on the wrong PR, sowwy)

Copy link
Member

@erikjohnston erikjohnston left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this looks sane

"stream_ordering": old_rotate_stream_ordering,
"last_receipt_stream_ordering": stream_ordering,
},
)

# Then any updated threads get their notification count and unread
# count updated.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This presumably handles the "main" thread too?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm actually having trouble convincing myself this doesn't need to handle thread_id being null separately.

If the summary exists above for the "main" thread than the upsert would fail, I think? (Since it would attempt to add a new row since null isn't equivalent null.)

I think that's possible, but probably not exercised by our tests?

}

# simple_upsert_many_txn doesn't support a predicate clause to force using
# the partial index (thread_id = NULL).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't that easy to add?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried a bit and was having issues with figuring out how the API should look -- a simple implementation which just accepts a string is pretty easy (I think), but a more complicated one which accepts keyvalues: Dict[str, Any] is a bit harder because you need to do conversion of = NULL to IS NULL. I'm a bit surprised we don't already have code for this?

Anyway, it is doable, yes. I can do it as a separate PR if you'd like.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, never mind if it isn't trivial. Was just a bit surprised is all.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at it, it should be doable. It would really simplify this code, so will take a look. 👍

Comment on lines +400 to +403
# XXX All threads should have the same stream ordering?
max_summary_stream_ordering = max(
summary_stream_ordering, max_summary_stream_ordering
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that stream ordering is the "max" stream ordering that we rotated from the event_push_actions table:

https://github.com/matrix-org/synapse/pull/13181/files#diff-f121377d76a7b35c60092da2c4bf8c849544459a2c676cf8e87057aff24ece54R1218

I think that means its equivalent to the stream ordering we rotated up to? They may differ but there should never be any relevant rows in EPA between the per-thread stream ordering and the max stream ordering?


-- Update the unique index for `event_push_summary`.
INSERT INTO background_updates (ordering, update_name, progress_json) VALUES
(7003, 'event_push_summary_unique_index2', '{}');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit worried that a bunch of the code will break while we don't have the proper index on event_push_summary? In particular I think if it encounters threads will try and insert multiple rows and fail due to the existing unique index?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that might be an issue -- would the solution be to drop the current index here and then add then new one in the background?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so, as we need a unique index to have upserts work? I have a horrible suspicion that we might need to either do this as a two step PR, one to add the column and new unique index, and the other to drop the index and start populating the thread rows. Either that or have a flag that changes the behaviour from the current behaviour to the new behaviour depending on if the index has finished being created?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Either of those seems doable. Do we have a preferred way of doing that? I think we'd have to wait for the new index regardless since we don't know for sure that an index was done just because we have a new version of Synapse?


ALTER TABLE event_push_actions_staging ADD COLUMN thread_id TEXT;

ALTER TABLE event_push_actions ADD COLUMN thread_id TEXT;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was not planning to do a background update to "fix" the thread_id for any existing event_push_actions rows -- I think with the way we summarize that's a somewhat futile effort and it should correct itself over time.

Comment on lines +16 to +18
ALTER TABLE event_push_actions_staging ADD COLUMN thread_id TEXT;

ALTER TABLE event_push_actions ADD COLUMN thread_id TEXT;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if the thread_id column should really live on the events table and not event_push_actions and event_push_actions_staging? This would be a bigger migration, but potentially more useful in the future? I don't have a strong opinion though.

@clokep
Copy link
Member Author

clokep commented Sep 12, 2022

Super-seeded by #13776, which is a manual rebase of this on top of #13753.

@clokep clokep closed this Sep 12, 2022
@clokep clokep deleted the clokep/thread-notifs branch September 14, 2022 19:18
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants