discourse/spec/models/post_timing_spec.rb
Sam Saffron f8eddd40ad PERF: remove avg_time calculations and regular jobs from posts and topics
After careful analysis of large data-sets it became apparent that avg_time
had no impact whatsoever on "best of" topic scoring. Calculating avg_time
was a very costly operation especially on large databases.

We have some longer term plans of introducing other weighting that is read
time based into our scoring for "best of" and "top" topics, but in the
interim to stop a large amount of work that is not achieving any value we
are removing the jobs.

Column removal will follow once we decide on a new replacement metric.
2019-05-06 15:59:01 +10:00

168 lines
5.3 KiB
Ruby

# frozen_string_literal: true
require 'rails_helper'
describe PostTiming do
it { is_expected.to validate_presence_of :post_number }
it { is_expected.to validate_presence_of :msecs }
describe 'pretend_read' do
let!(:p1) { Fabricate(:post) }
let!(:p2) { Fabricate(:post, topic: p1.topic, user: p1.user) }
let!(:p3) { Fabricate(:post, topic: p1.topic, user: p1.user) }
let :topic_id do
p1.topic_id
end
def timing(user_id, post_number)
PostTiming.create!(topic_id: topic_id, user_id: user_id, post_number: post_number, msecs: 0)
end
def topic_user(user_id, last_read_post_number, highest_seen_post_number)
TopicUser.create!(
topic_id: topic_id,
user_id: user_id,
last_read_post_number: last_read_post_number,
highest_seen_post_number: highest_seen_post_number
)
end
it 'works correctly' do
timing(1, 1)
timing(2, 1)
timing(2, 2)
timing(3, 1)
timing(3, 2)
timing(3, 3)
_tu_one = topic_user(1, 1, 1)
_tu_two = topic_user(2, 2, 2)
_tu_three = topic_user(3, 3, 3)
PostTiming.pretend_read(topic_id, 2, 3)
expect(PostTiming.where(topic_id: topic_id, user_id: 1, post_number: 3).count).to eq(0)
expect(PostTiming.where(topic_id: topic_id, user_id: 2, post_number: 3).count).to eq(1)
expect(PostTiming.where(topic_id: topic_id, user_id: 3, post_number: 3).count).to eq(1)
tu = TopicUser.find_by(topic_id: topic_id, user_id: 1)
expect(tu.last_read_post_number).to eq(1)
expect(tu.highest_seen_post_number).to eq(1)
tu = TopicUser.find_by(topic_id: topic_id, user_id: 2)
expect(tu.last_read_post_number).to eq(3)
expect(tu.highest_seen_post_number).to eq(3)
tu = TopicUser.find_by(topic_id: topic_id, user_id: 3)
expect(tu.last_read_post_number).to eq(3)
expect(tu.highest_seen_post_number).to eq(3)
end
end
describe 'safeguard' do
it "doesn't store timings that are larger than the account lifetime" do
user = Fabricate(:user, created_at: 3.minutes.ago)
post = Fabricate(:post)
PostTiming.process_timings(user, post.topic_id, 1, [[post.post_number, 123]])
msecs = PostTiming.where(post_number: post.post_number, user_id: user.id).pluck(:msecs)[0]
expect(msecs).to eq(123)
PostTiming.process_timings(user, post.topic_id, 1, [[post.post_number, 10.minutes.to_i * 1000]])
msecs = PostTiming.where(post_number: post.post_number, user_id: user.id).pluck(:msecs)[0]
expect(msecs).to eq(123 + PostTiming::MAX_READ_TIME_PER_BATCH)
end
end
describe 'process_timings' do
# integration tests
it 'processes timings correctly' do
PostActionNotifier.enable
post = Fabricate(:post)
(2..5).each do |i|
Fabricate(:post, topic: post.topic, post_number: i)
end
user2 = Fabricate(:coding_horror, created_at: 1.day.ago)
PostActionCreator.like(user2, post)
expect(post.user.unread_notifications).to eq(1)
PostTiming.process_timings(post.user, post.topic_id, 1, [[post.post_number, 100]])
post.user.reload
expect(post.user.unread_notifications).to eq(0)
PostTiming.process_timings(post.user, post.topic_id, 1, [[post.post_number, 1.day]])
user_visit = post.user.user_visits.order('id DESC').first
expect(user_visit.posts_read).to eq(1)
# Skip to bottom
PostTiming.process_timings(post.user, post.topic_id, 1, [[5, 100]])
expect(user_visit.reload.posts_read).to eq(2)
# Scroll up
PostTiming.process_timings(post.user, post.topic_id, 1, [[4, 100]])
expect(user_visit.reload.posts_read).to eq(3)
PostTiming.process_timings(post.user, post.topic_id, 1, [[2, 100], [3, 100]])
expect(user_visit.reload.posts_read).to eq(5)
end
it 'does not count private message posts read' do
pm = Fabricate(:private_message_topic, user: Fabricate(:admin))
user1, user2 = pm.topic_allowed_users.map(&:user)
(1..3).each do |i|
Fabricate(:post, topic: pm, user: user1)
end
PostTiming.process_timings(user2, pm.id, 10, [[1, 100]])
user_visit = user2.user_visits.last
expect(user_visit.posts_read).to eq(0)
PostTiming.process_timings(user2, pm.id, 10, [[2, 100], [3, 100]])
expect(user_visit.reload.posts_read).to eq(0)
end
end
describe 'recording' do
before do
@post = Fabricate(:post)
@topic = @post.topic
@coding_horror = Fabricate(:coding_horror)
@timing_attrs = { msecs: 1234, topic_id: @post.topic_id, user_id: @coding_horror.id, post_number: @post.post_number }
end
it 'adds a view to the post' do
expect {
PostTiming.record_timing(@timing_attrs)
@post.reload
}.to change(@post, :reads).by(1)
end
describe 'multiple calls' do
it 'correctly works' do
PostTiming.record_timing(@timing_attrs)
PostTiming.record_timing(@timing_attrs)
timing = PostTiming.find_by(topic_id: @post.topic_id, user_id: @coding_horror.id, post_number: @post.post_number)
expect(timing).to be_present
expect(timing.msecs).to eq(2468)
expect(@coding_horror.user_stat.posts_read_count).to eq(1)
end
end
end
end