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
This commit is contained in:
Régis Hanol 2021-03-28 10:25:30 +02:00 committed by GitHub
parent db7be947df
commit e8cad4bbf3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -83,32 +83,28 @@ class About
def category_moderators def category_moderators
allowed_cats = Guardian.new(@user).allowed_category_ids allowed_cats = Guardian.new(@user).allowed_category_ids
return [] if allowed_cats.blank? return [] if allowed_cats.blank?
cats_with_mods = Category.where.not(reviewable_by_group_id: nil).pluck(:id) cats_with_mods = Category.where.not(reviewable_by_group_id: nil).pluck(:id)
category_ids = cats_with_mods & allowed_cats category_ids = cats_with_mods & allowed_cats
return [] if category_ids.blank? return [] if category_ids.blank?
per_cat_limit = category_mods_limit / category_ids.size per_cat_limit = category_mods_limit / category_ids.size
per_cat_limit = 1 if per_cat_limit < 1 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 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 FROM categories c
CROSS JOIN LATERAL ( JOIN group_users gu ON gu.group_id = c.reviewable_by_group_id
SELECT ARRAY( JOIN users u ON u.id = gu.user_id
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) WHERE c.id IN (:category_ids)
GROUP BY c.id
ORDER BY c.position
SQL SQL
moderators = {}
User.where(id: results.map(&:user_ids).flatten.uniq).each do |user| moderators = User.where(id: results.map(&:user_ids).flatten.uniq).map { |u| [u.id, u] }.to_h
moderators[user.id] = user
end
moderators
results.map do |row| results.map do |row|
CategoryMods.new(row.category_id, row.user_ids.map { |id| moderators[id] }) CategoryMods.new(row.category_id, row.user_ids.map { |id| moderators[id] })
end end