DEV: Topic tracking state improvements (#12958)

Original PR which had to be reverted **https://github.com/discourse/discourse/pull/12555**. See the description there for what this PR is achieving, plus below.

The issue with the original PR is addressed in 92ef54f402

If you went to the `x unread` link for a tag Chrome would freeze up and possibly crash, or eventually unfreeze after nearly 10 mins. Other routes for unread/new were similarly slow. From profiling the issue was the `sync` function of `topic-tracking-state.js`, which calls down to `isNew` which in turn calls `moment`, a change I had made in the PR above. The time it takes locally with ~1400 topics in the tracking state is 2.3 seconds.

To solve this issue, I have moved these calculations for "created in new period" and "unread not too old" into the tracking state serializer.

When I was looking at the profiler I also noticed this issue which was just compounding the problem. Every time we modify topic tracking state we recalculate the sidebar tracking/everything/tag counts. However this calls `forEachTracked` and `countTags` which can be quite expensive as they go through the whole tracking state (and were also calling the removed moment functions).

I added some logs and this was being called 30 times when navigating to a new /unread route because  `sync` is being called from `build-topic-route` (one for each topic loaded due to pagination). So I just added a debounce here and it makes things even faster.

Finally, I changed topic tracking state to use a Map so our counts of the state keys is faster (Maps have .size whereas objects you have to do Object.keys(obj) which is O(n).)
This commit is contained in:
Martin Brennan
2021-05-31 09:22:28 +10:00
committed by GitHub
parent 1374ec45c5
commit 002c676344
14 changed files with 1442 additions and 481 deletions

View File

@ -331,7 +331,6 @@ class TopicQuery
list = TopicQuery.unread_filter(
list,
user.id,
staff: user.staff?
)
@ -379,14 +378,20 @@ class TopicQuery
end
end
def self.new_filter(list, treat_as_new_topic_start_date)
list.where("topics.created_at >= :created_at", created_at: treat_as_new_topic_start_date)
def self.new_filter(list, treat_as_new_topic_start_date: nil, treat_as_new_topic_clause_sql: nil)
if treat_as_new_topic_start_date
list = list.where("topics.created_at >= :created_at", created_at: treat_as_new_topic_start_date)
else
list = list.where("topics.created_at >= #{treat_as_new_topic_clause_sql}")
end
list
.where("tu.last_read_post_number IS NULL")
.where("COALESCE(tu.notification_level, :tracking) >= :tracking", tracking: TopicUser.notification_levels[:tracking])
end
def self.unread_filter(list, user_id, opts)
col_name = opts[:staff] ? "highest_staff_post_number" : "highest_post_number"
def self.unread_filter(list, staff: false)
col_name = staff ? "highest_staff_post_number" : "highest_post_number"
list
.where("tu.last_read_post_number < topics.#{col_name}")
@ -516,7 +521,6 @@ class TopicQuery
def unread_results(options = {})
result = TopicQuery.unread_filter(
default_results(options.reverse_merge(unordered: true)),
@user&.id,
staff: @user&.staff?)
.order('CASE WHEN topics.user_id = tu.user_id THEN 1 ELSE 2 END')
@ -547,7 +551,10 @@ class TopicQuery
def new_results(options = {})
# TODO does this make sense or should it be ordered on created_at
# it is ordering on bumped_at now
result = TopicQuery.new_filter(default_results(options.reverse_merge(unordered: true)), @user.user_option.treat_as_new_topic_start_date)
result = TopicQuery.new_filter(
default_results(options.reverse_merge(unordered: true)),
treat_as_new_topic_start_date: @user.user_option.treat_as_new_topic_start_date
)
result = remove_muted_topics(result, @user)
result = remove_muted_categories(result, @user, exclude: options[:category])
result = remove_muted_tags(result, @user, options)
@ -979,14 +986,16 @@ class TopicQuery
def new_messages(params)
TopicQuery
.new_filter(messages_for_groups_or_user(params[:my_group_ids]), Time.at(SiteSetting.min_new_topics_time).to_datetime)
.new_filter(
messages_for_groups_or_user(params[:my_group_ids]),
treat_as_new_topic_start_date: Time.at(SiteSetting.min_new_topics_time).to_datetime
)
.limit(params[:count])
end
def unread_messages(params)
query = TopicQuery.unread_filter(
messages_for_groups_or_user(params[:my_group_ids]),
@user.id,
staff: @user.staff?
)