PERF: Speed up home page unread+new query

PERF: Speed up home page unread+new query

restructure query so it avoids ORs

It appears postgres is picking suboptimal indexes if too many ORs exist despite how trivial the condition is.

This bypasses conditional in the query and evals them upfront.

On meta for my user this made a 10x perf difference.

This boils down to either having OR u.admin or not having OR u.admin in the query.

diff --git a/app/models/topic_tracking_state.rb b/app/models/topic_tracking_state.rb
index d56dda7..6857341 100644
--- a/app/models/topic_tracking_state.rb
+++ b/app/models/topic_tracking_state.rb
@@ -168,11 +168,24 @@ class TopicTrackingState
     #
     # This code needs to be VERY efficient as it is triggered via the message bus and may steal
     #  cycles from usual requests
-    #
-    #
-    sql = report_raw_sql(topic_id: topic_id, skip_unread: true, skip_order: true, staff: user.staff?)
+    sql = report_raw_sql(
+      topic_id: topic_id,
+      skip_unread: true,
+      skip_order: true,
+      staff: user.staff?,
+      admin: user.admin?
+    )
+
     sql << "\nUNION ALL\n\n"
-    sql << report_raw_sql(topic_id: topic_id, skip_new: true, skip_order: true, staff: user.staff?, filter_old_unread: true)
+
+    sql << report_raw_sql(
+      topic_id: topic_id,
+      skip_new: true,
+      skip_order: true,
+      staff: user.staff?,
+      filter_old_unread: true,
+      admin: user.admin?
+    )
 
     DB.query(
       sql,
@@ -183,9 +196,10 @@ class TopicTrackingState
   end
 
   def self.report_raw_sql(opts = nil)
+    opts ||= {}
 
     unread =
-      if opts && opts[:skip_unread]
+      if opts[:skip_unread]
         "1=0"
       else
         TopicQuery
@@ -196,30 +210,54 @@ class TopicTrackingState
       end
 
     filter_old_unread =
-      if opts && opts[:filter_old_unread]
+      if opts[:filter_old_unread]
         " topics.updated_at >= us.first_unread_at AND "
       else
         ""
       end
 
     new =
-      if opts && opts[:skip_new]
+      if opts[:skip_new]
         "1=0"
       else
         TopicQuery.new_filter(Topic, "xxx").where_clause.send(:predicates).join(" AND ").gsub!("'xxx'", treat_as_new_topic_clause) +
           " AND topics.created_at > :min_new_topic_date"
       end
 
-    select = (opts && opts[:select]) || "
+    select = (opts[:select]) || "
            u.id AS user_id,
            topics.id AS topic_id,
            topics.created_at,
-           #{opts && opts[:staff] ? "highest_staff_post_number highest_post_number" : "highest_post_number"},
+           #{opts[:staff] ? "highest_staff_post_number highest_post_number" : "highest_post_number"},
            last_read_post_number,
            c.id AS category_id,
            tu.notification_level"
 
-    sql = +<<SQL
+    category_filter =
+      if opts[:admin]
+        ""
+      else
+        append = "OR u.admin" if !opts.key?(:admin)
+        <<~SQL
+          (
+           NOT c.read_restricted #{append} OR category_id IN (
+              SELECT c2.id FROM categories c2
+              JOIN category_groups cg ON cg.category_id = c2.id
+              JOIN group_users gu ON gu.user_id = :user_id AND cg.group_id = gu.group_id
+              WHERE c2.read_restricted )
+          ) AND
+        SQL
+      end
+
+    visibility_filter =
+      if opts[:staff]
+        ""
+      else
+        append = "OR u.admin OR u.moderator" if !opts.key?(:staff)
+        "(topics.visible #{append}) AND"
+      end
+
+    sql = +<<~SQL
     SELECT #{select}
     FROM topics
     JOIN users u on u.id = :user_id
@@ -231,15 +269,10 @@ class TopicTrackingState
           #{filter_old_unread}
           topics.archetype <> 'private_message' AND
           ((#{unread}) OR (#{new})) AND
-          (topics.visible OR u.admin OR u.moderator) AND
+          #{visibility_filter}
           topics.deleted_at IS NULL AND
-          ( NOT c.read_restricted OR u.admin OR category_id IN (
-              SELECT c2.id FROM categories c2
-              JOIN category_groups cg ON cg.category_id = c2.id
-              JOIN group_users gu ON gu.user_id = :user_id AND cg.group_id = gu.group_id
-              WHERE c2.read_restricted )
-          )
-          AND NOT EXISTS( SELECT 1 FROM category_users cu
+          #{category_filter}
+          NOT EXISTS( SELECT 1 FROM category_users cu
                           WHERE last_read_post_number IS NULL AND
                                cu.user_id = :user_id AND
                                cu.category_id = topics.category_id AND
@@ -247,11 +280,11 @@ class TopicTrackingState
 
 SQL
 
-    if opts && opts[:topic_id]
+    if opts[:topic_id]
       sql << " AND topics.id = :topic_id"
     end
 
-    unless opts && opts[:skip_order]
+    unless opts[:skip_order]
       sql << " ORDER BY topics.bumped_at DESC"
     end

GitHub sha: 81a1bafe