PERF: improve category moderators query (#12538)

PERF: improve category moderators query (#12538)

In the about page, we list a certain number of category moderators.

This rewrites the SQL query used to retrieve the most recent category moderators in order to perform better with a large number of users/categories/category moderators.

TIL: you can ORDER BY inside an ARRAY_AGG in postgres TIL: you can slide ARRAYS in postgres

diff --git a/app/models/about.rb b/app/models/about.rb
index df2d3ac..5631fbb 100644
--- a/app/models/about.rb
+++ b/app/models/about.rb
@@ -83,32 +83,28 @@ class About
   def category_moderators
     allowed_cats = Guardian.new(@user).allowed_category_ids
     return [] if allowed_cats.blank?
+
     cats_with_mods = Category.where.not(reviewable_by_group_id: nil).pluck(:id)
+
     category_ids = cats_with_mods & allowed_cats
     return [] if category_ids.blank?
 
     per_cat_limit = category_mods_limit / category_ids.size
     per_cat_limit = 1 if per_cat_limit < 1
-    results = DB.query(<<~SQL, category_ids: category_ids, per_cat_limit: per_cat_limit)
-      SELECT c.id category_id, user_ids
-      FROM categories c
-      CROSS JOIN LATERAL (
-        SELECT ARRAY(
-          SELECT u.id
-          FROM users u
-          JOIN group_users gu
-          ON gu.group_id = c.reviewable_by_group_id AND gu.user_id = u.id
-          ORDER BY last_seen_at DESC
-          LIMIT :per_cat_limit
-        ) AS user_ids
-      ) user_ids
-      WHERE c.id IN (:category_ids)
+
+    results = DB.query(<<~SQL, category_ids: category_ids)
+        SELECT c.id category_id
+             , (ARRAY_AGG(u.id ORDER BY u.last_seen_at DESC))[:#{per_cat_limit}] user_ids
+          FROM categories c
+          JOIN group_users gu ON gu.group_id = c.reviewable_by_group_id
+          JOIN users u ON u.id = gu.user_id
+         WHERE c.id IN (:category_ids)
+      GROUP BY c.id
+      ORDER BY c.position
     SQL
-    moderators = {}
-    User.where(id: results.map(&:user_ids).flatten.uniq).each do |user|
-      moderators[user.id] = user
-    end
-    moderators
+
+    moderators = User.where(id: results.map(&:user_ids).flatten.uniq).map { |u| [u.id, u] }.to_h
+
     results.map do |row|
       CategoryMods.new(row.category_id, row.user_ids.map { |id| moderators[id] })
     end

GitHub sha: e8cad4bb

This commit appears in #12538 which was approved by OsamaSayegh. It was merged by ZogStriP.