PERF: batch update post timings

previously we would issue a query per row in post timings,
this batches it
This commit is contained in:
Sam 2015-06-18 17:02:10 +10:00
parent 61df4bd90a
commit f0c74d7685
1 changed files with 56 additions and 25 deletions

View File

@ -29,6 +29,25 @@ class PostTiming < ActiveRecord::Base
TopicUser.ensure_consistency!(topic_id)
end
def self.record_new_timing(args)
begin
exec_sql("INSERT INTO post_timings (topic_id, user_id, post_number, msecs)
SELECT :topic_id, :user_id, :post_number, :msecs
WHERE NOT EXISTS(SELECT 1 FROM post_timings
WHERE topic_id = :topic_id
AND user_id = :user_id
AND post_number = :post_number)",
args)
rescue PG::UniqueViolation
# concurrency is hard, we are not running serialized so this can possibly
# still happen, if it happens we just don't care, its an invalid record anyway
return
end
Post.where(['topic_id = :topic_id and post_number = :post_number', args]).update_all 'reads = reads + 1'
UserStat.where(user_id: args[:user_id]).update_all 'posts_read_count = posts_read_count + 1'
end
# Increases a timer if a row exists, otherwise create it
def self.record_timing(args)
rows = exec_sql_row_count("UPDATE post_timings
@ -38,25 +57,7 @@ class PostTiming < ActiveRecord::Base
AND post_number = :post_number",
args)
if rows == 0
begin
exec_sql("INSERT INTO post_timings (topic_id, user_id, post_number, msecs)
SELECT :topic_id, :user_id, :post_number, :msecs
WHERE NOT EXISTS(SELECT 1 FROM post_timings
WHERE topic_id = :topic_id
AND user_id = :user_id
AND post_number = :post_number)",
args)
rescue PG::UniqueViolation
# concurrency is hard, we are not running serialized so this can possibly
# still happen, if it happens we just don't care, its an invalid record anyway
return
end
Post.where(['topic_id = :topic_id and post_number = :post_number', args]).update_all 'reads = reads + 1'
UserStat.where(user_id: args[:user_id]).update_all 'posts_read_count = posts_read_count + 1'
end
record_new_timing(args) if rows == 0
end
@ -74,15 +75,45 @@ class PostTiming < ActiveRecord::Base
account_age_msecs = ((Time.now - current_user.created_at) * 1000.0)
highest_seen = 1
timings.each do |post_number, time|
if post_number >= 0 && time < account_age_msecs
PostTiming.record_timing(topic_id: topic_id,
post_number: post_number,
user_id: current_user.id,
msecs: time)
join_table = []
timings = timings.find_all do |post_number, time|
post_number >= 0 && time < account_age_msecs
end
timings.each_with_index do |(post_number, time), index|
join_table << "SELECT #{topic_id.to_i} topic_id, #{post_number.to_i} post_number,
#{current_user.id.to_i} user_id, #{time.to_i} msecs, #{index} idx"
highest_seen = post_number.to_i > highest_seen ?
post_number.to_i : highest_seen
end
if join_table.length > 0
sql = <<SQL
UPDATE post_timings t
SET msecs = t.msecs + x.msecs
FROM (#{join_table.join(" UNION ALL ")}) x
WHERE x.topic_id = t.topic_id AND
x.post_number = t.post_number AND
x.user_id = t.user_id
RETURNING x.idx
SQL
result = exec_sql(sql)
result.type_map = SqlBuilder.pg_type_map
existing = Set.new(result.column_values(0))
timings.each_with_index do |(post_number, time),index|
unless existing.include?(index)
PostTiming.record_new_timing(topic_id: topic_id,
post_number: post_number,
user_id: current_user.id,
msecs: time)
end
end
end