mirror of
https://github.com/discourse/discourse.git
synced 2025-06-04 21:04:40 +08:00
PERF: improve performance of unread queries
Figuring out what unread topics a user has is a very expensive operation over time. Users can easily accumulate 10s of thousands of tracking state rows (1 for every topic they ever visit) When figuring out what a user has that is unread we need to join the tracking state records to the topic table. This can very quickly lead to cases where you need to scan through the entire topic table. This commit optimises it so we always keep track of the "first" date a user has unread topics. Then we can easily filter out all earlier topics from the join. We use pg functions, instead of nested queries here to assist the planner.
This commit is contained in:
@ -280,10 +280,21 @@ class TopicQuery
|
||||
.where("COALESCE(tu.notification_level, :tracking) >= :tracking", tracking: TopicUser.notification_levels[:tracking])
|
||||
end
|
||||
|
||||
def self.unread_filter(list, opts)
|
||||
def self.unread_filter(list, user_id, opts)
|
||||
# PERF note
|
||||
# We use the function first_unread_topic_for here instead of joining
|
||||
# the table to assist the PostgreSQL query planner
|
||||
#
|
||||
# We want the query planner to have the actual value of the first_unread_topic so
|
||||
# it can pick an appropriate plan. If it does not have this upfront it will just assume
|
||||
# that the value will be 1/3 of the way through the topic table which makes it use terrible
|
||||
# indexes for the plan.
|
||||
#
|
||||
col_name = opts[:staff] ? "highest_staff_post_number" : "highest_post_number"
|
||||
|
||||
list.where("tu.last_read_post_number < topics.#{col_name}")
|
||||
list
|
||||
.where("tu.last_read_post_number < topics.#{col_name}")
|
||||
.where("topics.last_unread_at >= first_unread_topic_for(?)", user_id)
|
||||
.where("COALESCE(tu.notification_level, :regular) >= :tracking",
|
||||
regular: TopicUser.notification_levels[:regular], tracking: TopicUser.notification_levels[:tracking])
|
||||
end
|
||||
@ -361,7 +372,10 @@ class TopicQuery
|
||||
end
|
||||
|
||||
def unread_results(options={})
|
||||
result = TopicQuery.unread_filter(default_results(options.reverse_merge(:unordered => true)), staff: @user.try(:staff?))
|
||||
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')
|
||||
|
||||
self.class.results_filter_callbacks.each do |filter_callback|
|
||||
@ -724,7 +738,10 @@ class TopicQuery
|
||||
end
|
||||
|
||||
def unread_messages(params)
|
||||
TopicQuery.unread_filter(messages_for_groups_or_user(params[:my_group_ids]), staff: @user.try(:staff?))
|
||||
TopicQuery.unread_filter(
|
||||
messages_for_groups_or_user(params[:my_group_ids]),
|
||||
@user&.id,
|
||||
staff: @user&.staff?)
|
||||
.limit(params[:count])
|
||||
end
|
||||
|
||||
|
Reference in New Issue
Block a user