PERF: reduce queries required for post timings
- also freezes a bunch of strings - bypass active record for an exists query
This commit is contained in:
parent
8c47eb2951
commit
b7023da894
|
@ -1,3 +1,5 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
#
|
||||||
require_dependency 'archetype'
|
require_dependency 'archetype'
|
||||||
|
|
||||||
class PostTiming < ActiveRecord::Base
|
class PostTiming < ActiveRecord::Base
|
||||||
|
@ -76,7 +78,7 @@ class PostTiming < ActiveRecord::Base
|
||||||
MAX_READ_TIME_PER_BATCH = 60 * 1000.0
|
MAX_READ_TIME_PER_BATCH = 60 * 1000.0
|
||||||
|
|
||||||
def self.process_timings(current_user, topic_id, topic_time, timings, opts = {})
|
def self.process_timings(current_user, topic_id, topic_time, timings, opts = {})
|
||||||
current_user.user_stat.update_time_read!
|
UserStat.update_time_read!(current_user.id)
|
||||||
|
|
||||||
max_time_per_post = ((Time.now - current_user.created_at) * 1000.0)
|
max_time_per_post = ((Time.now - current_user.created_at) * 1000.0)
|
||||||
max_time_per_post = MAX_READ_TIME_PER_BATCH if max_time_per_post > MAX_READ_TIME_PER_BATCH
|
max_time_per_post = MAX_READ_TIME_PER_BATCH if max_time_per_post > MAX_READ_TIME_PER_BATCH
|
||||||
|
@ -103,8 +105,7 @@ class PostTiming < ActiveRecord::Base
|
||||||
end
|
end
|
||||||
|
|
||||||
if join_table.length > 0
|
if join_table.length > 0
|
||||||
sql = <<SQL
|
sql = <<~SQL
|
||||||
|
|
||||||
UPDATE post_timings t
|
UPDATE post_timings t
|
||||||
SET msecs = t.msecs + x.msecs
|
SET msecs = t.msecs + x.msecs
|
||||||
FROM (#{join_table.join(" UNION ALL ")}) x
|
FROM (#{join_table.join(" UNION ALL ")}) x
|
||||||
|
@ -117,7 +118,16 @@ SQL
|
||||||
result = exec_sql(sql)
|
result = exec_sql(sql)
|
||||||
result.type_map = SqlBuilder.pg_type_map
|
result.type_map = SqlBuilder.pg_type_map
|
||||||
existing = Set.new(result.column_values(0))
|
existing = Set.new(result.column_values(0))
|
||||||
new_posts_read = timings.size - existing.size if Topic.where(id: topic_id, archetype: Archetype.default).exists?
|
|
||||||
|
sql = <<~SQL
|
||||||
|
SELECT 1 FROM topics
|
||||||
|
WHERE deleted_at IS NULL AND
|
||||||
|
archetype = 'regular' AND
|
||||||
|
id = :topic_id
|
||||||
|
SQL
|
||||||
|
|
||||||
|
is_regular = Post.exec_sql(sql, topic_id: topic_id).cmd_tuples == 1
|
||||||
|
new_posts_read = timings.size - existing.size if is_regular
|
||||||
|
|
||||||
timings.each_with_index do |(post_number, time), index|
|
timings.each_with_index do |(post_number, time), index|
|
||||||
unless existing.include?(index)
|
unless existing.include?(index)
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
class UserStat < ActiveRecord::Base
|
class UserStat < ActiveRecord::Base
|
||||||
|
|
||||||
belongs_to :user
|
belongs_to :user
|
||||||
|
@ -68,42 +69,44 @@ class UserStat < ActiveRecord::Base
|
||||||
|
|
||||||
MAX_TIME_READ_DIFF = 100
|
MAX_TIME_READ_DIFF = 100
|
||||||
# attempt to add total read time to user based on previous time this was called
|
# attempt to add total read time to user based on previous time this was called
|
||||||
def update_time_read!
|
def self.update_time_read!(id)
|
||||||
if last_seen = last_seen_cached
|
if last_seen = last_seen_cached(id)
|
||||||
diff = (Time.now.to_f - last_seen.to_f).round
|
diff = (Time.now.to_f - last_seen.to_f).round
|
||||||
if diff > 0 && diff < MAX_TIME_READ_DIFF
|
if diff > 0 && diff < MAX_TIME_READ_DIFF
|
||||||
update_args = ["time_read = time_read + ?", diff]
|
update_args = ["time_read = time_read + ?", diff]
|
||||||
UserStat.where(user_id: id, time_read: time_read).update_all(update_args)
|
UserStat.where(user_id: id).update_all(update_args)
|
||||||
UserVisit.where(user_id: id, visited_at: Time.zone.now.to_date).update_all(update_args)
|
UserVisit.where(user_id: id, visited_at: Time.zone.now.to_date).update_all(update_args)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
cache_last_seen(Time.now.to_f)
|
cache_last_seen(id, Time.now.to_f)
|
||||||
|
end
|
||||||
|
|
||||||
|
def update_time_read!
|
||||||
|
UserStat.update_time_read!(id)
|
||||||
end
|
end
|
||||||
|
|
||||||
def reset_bounce_score!
|
def reset_bounce_score!
|
||||||
update_columns(reset_bounce_score_after: nil, bounce_score: 0)
|
update_columns(reset_bounce_score_after: nil, bounce_score: 0)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.last_seen_key(id)
|
||||||
|
# frozen
|
||||||
|
-"user-last-seen:#{id}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.last_seen_cached(id)
|
||||||
|
$redis.get(last_seen_key(id))
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.cache_last_seen(id, val)
|
||||||
|
$redis.set(last_seen_key(id), val)
|
||||||
|
end
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
def trigger_badges
|
def trigger_badges
|
||||||
BadgeGranter.queue_badge_grant(Badge::Trigger::UserChange, user: self.user)
|
BadgeGranter.queue_badge_grant(Badge::Trigger::UserChange, user: self.user)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def last_seen_key
|
|
||||||
@last_seen_key ||= "user-last-seen:#{id}"
|
|
||||||
end
|
|
||||||
|
|
||||||
def last_seen_cached
|
|
||||||
$redis.get(last_seen_key)
|
|
||||||
end
|
|
||||||
|
|
||||||
def cache_last_seen(val)
|
|
||||||
$redis.set(last_seen_key, val)
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# == Schema Information
|
# == Schema Information
|
||||||
|
|
|
@ -77,22 +77,26 @@ describe UserStat do
|
||||||
let(:stat) { user.user_stat }
|
let(:stat) { user.user_stat }
|
||||||
|
|
||||||
it 'makes no changes if nothing is cached' do
|
it 'makes no changes if nothing is cached' do
|
||||||
stat.expects(:last_seen_cached).returns(nil)
|
$redis.del(UserStat.last_seen_key(user.id))
|
||||||
stat.update_time_read!
|
stat.update_time_read!
|
||||||
stat.reload
|
stat.reload
|
||||||
expect(stat.time_read).to eq(0)
|
expect(stat.time_read).to eq(0)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'makes a change if time read is below threshold' do
|
it 'makes a change if time read is below threshold' do
|
||||||
stat.expects(:last_seen_cached).returns(Time.now - 10)
|
freeze_time
|
||||||
|
UserStat.cache_last_seen(user.id, (Time.now - 10).to_f)
|
||||||
stat.update_time_read!
|
stat.update_time_read!
|
||||||
stat.reload
|
stat.reload
|
||||||
expect(stat.time_read).to eq(10)
|
expect(stat.time_read).to eq(10)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'makes no change if time read is above threshold' do
|
it 'makes no change if time read is above threshold' do
|
||||||
|
freeze_time
|
||||||
|
|
||||||
t = Time.now - 1 - UserStat::MAX_TIME_READ_DIFF
|
t = Time.now - 1 - UserStat::MAX_TIME_READ_DIFF
|
||||||
stat.expects(:last_seen_cached).returns(t)
|
UserStat.cache_last_seen(user.id, t.to_f)
|
||||||
|
|
||||||
stat.update_time_read!
|
stat.update_time_read!
|
||||||
stat.reload
|
stat.reload
|
||||||
expect(stat.time_read).to eq(0)
|
expect(stat.time_read).to eq(0)
|
||||||
|
|
Loading…
Reference in New Issue