PERF: avoid expensive order by random for suggested topics

This commit is contained in:
Sam 2015-02-25 17:19:12 +11:00
parent 7b6e85cb6c
commit b760d22460
3 changed files with 112 additions and 1 deletions

View File

@ -0,0 +1,75 @@
class RandomTopicSelector
BACKFILL_SIZE = 3000
BACKFILL_LOW_WATER_MARK = 500
def self.backfill(category=nil)
exclude = category.try(:topic_id)
# don't leak private categories into the "everything" group
user = category ? CategoryFeaturedTopic.fake_admin : nil
options = {
per_page: SiteSetting.category_featured_topics,
visible: true,
no_definitions: true
}
options[:except_topic_ids] = [category.topic_id] if exclude
options[:category] = category.id if category
query = TopicQuery.new(user, options)
results = query.latest_results.order('RANDOM()')
.where(closed: false, archived: false)
.limit(BACKFILL_SIZE)
.reorder('RANDOM()')
.pluck(:id)
key = cache_key(category)
results.each do |id|
$redis.rpush(key, id)
end
$redis.expire(key, 2.days)
results
end
def self.next(count, category=nil)
key = cache_key(category)
results = []
left = count
while left > 0
id = $redis.lpop key
break unless id
results << id.to_i
left -= 1
end
backfilled = false
if left > 0
ids = backfill(category)
backfilled = true
results += ids[0...count]
results.uniq!
results = results[0...count]
end
if !backfilled && $redis.llen(key) < BACKFILL_LOW_WATER_MARK
Scheduler::Defer.later("backfill") do
backfill(category)
end
end
results
end
def self.cache_key(category=nil)
"random_topic_cache_#{category.try(:id)}"
end
end

View File

@ -414,7 +414,16 @@ class TopicQuery
result = result.order("CASE WHEN topics.category_id = #{topic.category_id.to_i} THEN 0 ELSE 1 END")
end
result.order("RANDOM()")
# Best effort, it over selects, however if you have a high number
# of muted categories there is tiny chance we will not select enough
# in particular this can happen if current category is empty and tons
# of muted, big edge case
#
# we over select in case cache is stale
max = (count*1.3).to_i
ids = RandomTopicSelector.next(max) + RandomTopicSelector.next(max, topic.category)
result.where(id: ids)
end
def suggested_ordering(result, options)

View File

@ -0,0 +1,27 @@
require 'spec_helper'
describe RandomTopicSelector do
it 'can correctly use cache' do
key = RandomTopicSelector.cache_key
$redis.del key
4.times do |t|
$redis.rpush key, t
end
RandomTopicSelector.next(2).should == [0,1]
RandomTopicSelector.next(2).should == [2,3]
end
it 'can correctly backfill' do
category = Fabricate(:category)
t1 = Fabricate(:topic, category_id: category.id)
_t2 = Fabricate(:topic, category_id: category.id, visible: false)
_t3 = Fabricate(:topic, category_id: category.id, deleted_at: 1.minute.ago)
t4 = Fabricate(:topic, category_id: category.id)
RandomTopicSelector.next(5, category).sort.should == [t1.id,t4.id].sort
end
end