Skip to content

Commit 52d544e

Browse files
PERF: Optimize CategoryList#relevant_topics_query SQL query (stable backport) (#36571)
Topics marked as category featured topics should always have `Topic#category_id` set so the `topics.category_id is null` predicate is not necessary. Profiling the query also reviewes that the `topics.category_id is null` predicate is causing the PG planner to do a seq scan against the topics table on large sites. Backports #36476 Co-authored-by: Alan Guo Xiang Tan <[email protected]>
1 parent 5d3dd30 commit 52d544e

File tree

3 files changed

+22
-3
lines changed

3 files changed

+22
-3
lines changed

app/models/category_list.rb

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,11 @@ def self.order_categories(categories)
7676
def relevant_topics_query
7777
@all_topics =
7878
Topic
79-
.secured(@guardian)
79+
.secured(
80+
@guardian,
81+
include_uncategorized: false,
82+
) # perf optimization since category featured topics can't have `category_id` set to null
83+
.listable_topics
8084
.joins(
8185
"INNER JOIN category_featured_topics ON topics.id = category_featured_topics.topic_id",
8286
)

app/models/topic.rb

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,7 @@ def recover!(recovered_by = nil)
366366
scope :exclude_scheduled_bump_topics, -> { where.not(id: TopicTimer.scheduled_bump_topics) }
367367

368368
scope :secured,
369-
lambda { |guardian = nil|
369+
lambda { |guardian = nil, include_uncategorized: true|
370370
ids = guardian.secure_category_ids if guardian
371371

372372
# Query conditions
@@ -377,8 +377,10 @@ def recover!(recovered_by = nil)
377377
["NOT read_restricted"]
378378
end
379379

380+
uncategorized_condition = "topics.category_id IS NULL OR" if include_uncategorized
381+
380382
where(
381-
"topics.category_id IS NULL OR topics.category_id IN (SELECT id FROM categories WHERE #{condition[0]})",
383+
"#{uncategorized_condition} topics.category_id IN (SELECT id FROM categories WHERE #{condition[0]})",
382384
condition[1],
383385
)
384386
}

spec/models/topic_spec.rb

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2543,6 +2543,19 @@ def set_state!(group, user, state)
25432543
topic,
25442544
)
25452545
end
2546+
2547+
it "excludes uncategorized topics when `include_uncategorized` kwarg is false" do
2548+
uncategorized_topic =
2549+
Fabricate(:topic, category_id: nil, archetype: Archetype.private_message)
2550+
2551+
categorized_topic = Fabricate(:topic, category: Fabricate(:category))
2552+
2553+
expect(uncategorized_topic.reload.category_id).to eq(nil)
2554+
2555+
result = Topic.secured(Guardian.new(user), include_uncategorized: false)
2556+
2557+
expect(result.pluck(:id)).to contain_exactly(categorized_topic.id)
2558+
end
25462559
end
25472560

25482561
describe "all_allowed_users" do

0 commit comments

Comments
 (0)