diff --git a/app/models/topic_hot_score.rb b/app/models/topic_hot_score.rb index 5f40ad08438..bb71a8ab1b3 100644 --- a/app/models/topic_hot_score.rb +++ b/app/models/topic_hot_score.rb @@ -102,9 +102,26 @@ class TopicHotScore < ActiveRecord::Base WHERE thsOrig.topic_id = ths.topic_id SQL - # update up to BATCH_SIZE records that are out of date based on age - # we need an extra index for this - DB.exec(<<~SQL, args) + # we may end up update 2x batch size, this is ok + # we need to update 1 batch of high scoring topics + # we need to update a second batch of recently bumped topics + sql = <<~SQL + WITH topic_ids AS ( + SELECT topic_id FROM ( + SELECT th3.topic_id FROM topic_hot_scores th3 + JOIN topics t3 on t3.id = th3.topic_id + ORDER BY t3.bumped_at DESC + LIMIT :max + ) Y + + UNION ALL + + SELECT topic_id FROM ( + SELECT th2.topic_id FROM topic_hot_scores th2 + ORDER BY th2.score DESC, th2.recent_first_bumped_at DESC NULLS LAST + LIMIT :max + ) X + ) UPDATE topic_hot_scores ths SET score = ( CASE WHEN topics.created_at > :recent_cutoff @@ -121,11 +138,11 @@ class TopicHotScore < ActiveRecord::Base FROM topics WHERE topics.id IN ( - SELECT topic_id FROM topic_hot_scores - ORDER BY score DESC, recent_first_bumped_at DESC NULLS LAST - LIMIT :max + SELECT topic_id FROM topic_ids ) AND ths.topic_id = topics.id SQL + + DB.exec(sql, args) end end diff --git a/spec/models/topic_hot_scores_spec.rb b/spec/models/topic_hot_scores_spec.rb index 407890286a5..836315b1223 100644 --- a/spec/models/topic_hot_scores_spec.rb +++ b/spec/models/topic_hot_scores_spec.rb @@ -6,6 +6,28 @@ RSpec.describe TopicHotScore do fab!(:user2) { Fabricate(:user) } fab!(:user3) { Fabricate(:user) } + it "also always updates based on recent activity" do + freeze_time + + # this will come in with a score + topic = Fabricate(:topic, created_at: 1.hour.ago, bumped_at: 2.minutes.ago) + post = Fabricate(:post, topic: topic, created_at: 2.minute.ago) + PostActionCreator.like(user, post) + + TopicHotScore.update_scores + + # this will come in in the batch in score 0 + topic = Fabricate(:topic, created_at: 1.minute.ago, bumped_at: 1.minute.ago) + post = Fabricate(:post, topic: topic, created_at: 1.minute.ago) + PostActionCreator.like(user, post) + + # batch size is 1 so if we do not do something special we only update + # the high score topic and skip new + TopicHotScore.update_scores(1) + + expect(TopicHotScore.find_by(topic_id: topic.id).score).to be_within(0.001).of(0.861) + end + it "can correctly update like counts and post counts and account for activity" do freeze_time