PERF: improve performance of category topic list

In some cases CTE caused pathologically bad query plans.
This optimises it so query runs by itself and caches for lifetime
of the topic query object.

This lightweight caching is done cause topic query will often
execute two queries (one for pinned and one for non pinned)
This commit is contained in:
Sam Saffron 2020-02-29 15:40:54 +11:00
parent 18209e1daf
commit b4999acadd
No known key found for this signature in database
GPG Key ID: B9606168D2FFD9F5
1 changed files with 27 additions and 13 deletions

View File

@ -669,20 +669,11 @@ class TopicQuery
if options[:no_subcategories]
result = result.where('categories.id = ?', category_id)
else
sql = <<~SQL
categories.id IN (
WITH RECURSIVE subcategories AS (
SELECT :category_id id, 1 depth
UNION
SELECT categories.id, (subcategories.depth + 1) depth
FROM categories
JOIN subcategories ON subcategories.id = categories.parent_category_id
WHERE subcategories.depth < :max_category_nesting
)
SELECT subcategories.id FROM subcategories
) AND (categories.id = :category_id OR topics.id != categories.topic_id)
result = result.where(<<~SQL, subcategory_ids: subcategory_ids(category_id), category_id: category_id)
categories.id in (:subcategory_ids) AND (
categories.topic_id <> topics.id OR categories.id = :category_id
)
SQL
result = result.where(sql, category_id: category_id, max_category_nesting: SiteSetting.max_category_nesting)
end
result = result.references(:categories)
@ -1061,6 +1052,29 @@ class TopicQuery
private
def subcategory_ids(category_id)
@subcategory_ids ||= {}
@subcategory_ids[category_id] ||=
begin
sql = <<~SQL
WITH RECURSIVE subcategories AS (
SELECT :category_id id, 1 depth
UNION
SELECT categories.id, (subcategories.depth + 1) depth
FROM categories
JOIN subcategories ON subcategories.id = categories.parent_category_id
WHERE subcategories.depth < :max_category_nesting
)
SELECT id FROM subcategories
SQL
DB.query_single(
sql,
category_id: category_id,
max_category_nesting: SiteSetting.max_category_nesting
)
end
end
def sanitize_sql_array(input)
ActiveRecord::Base.public_send(:sanitize_sql_array, input.join(','))
end