PERF: optimize lookup of reviewable info in post stream

This previously was a hot path in topic view. Avoids an expensive active
record operation and instead perform SQL directly which is far more
targeted and efficient
This commit is contained in:
Sam Saffron 2019-06-07 18:12:30 +10:00
parent cbd4d06da0
commit f88dced0b7
2 changed files with 58 additions and 20 deletions

View File

@ -421,17 +421,32 @@ class TopicView
def reviewable_counts
if @reviewable_counts.blank?
# Create a hash with counts by post so we can quickly look up whether there is reviewable content.
@reviewable_counts = {}
Reviewable.
where(target_type: 'Post', target_id: @posts.map(&:id)).
includes(:reviewable_scores).each do |r|
post_ids = @posts.map(&:id)
for_post = (@reviewable_counts[r.target_id] ||= { total: 0, pending: 0, reviewable_id: r.id })
r.reviewable_scores.each do |s|
for_post[:total] += 1
for_post[:pending] += 1 if s.pending?
end
sql = <<~SQL
SELECT target_id,
MAX(r.id) reviewable_id,
COUNT(*) total,
SUM(CASE WHEN s.status = :pending THEN 1 ELSE 0 END) pending
FROM reviewables r
JOIN reviewable_scores s ON reviewable_id = r.id
WHERE r.target_id IN (:post_ids) AND
r.target_type = 'Post'
GROUP BY target_id
SQL
@reviewable_counts = {}
DB.query(
sql,
pending: ReviewableScore.statuses[:pending],
post_ids: post_ids
).each do |row|
@reviewable_counts[row.target_id] = {
total: row.total,
pending: row.pending,
reviewable_id: row.reviewable_id
}
end
end

View File

@ -2,28 +2,51 @@
require 'rails_helper'
RSpec.describe TopicViewPostsSerializer do
fab!(:user) { Fabricate(:user) }
fab!(:post) { Fabricate(:post) }
let(:topic) { post.topic }
let(:topic_view) { TopicView.new(topic, user, post_ids: [post.id]) }
describe TopicViewPostsSerializer do
subject do
described_class.new(topic_view,
it 'should return the right attributes' do
user = Fabricate(:user)
post = Fabricate(:post)
topic = post.topic
reviewable = Fabricate(:reviewable_flagged_post, created_by: user, target: post, topic: topic)
ReviewableScore.create!(
reviewable_id: reviewable.id,
user_id: user.id,
reviewable_score_type: 0,
status: ReviewableScore.statuses[:pending]
)
ReviewableScore.create!(
reviewable_id: reviewable.id,
user_id: user.id,
reviewable_score_type: 0,
status: ReviewableScore.statuses[:ignored]
)
topic_view = TopicView.new(topic, user, post_ids: [post.id])
serializer = TopicViewPostsSerializer.new(
topic_view,
scope: Guardian.new(Fabricate(:admin)),
root: false
)
end
it 'should return the right attributes' do
body = JSON.parse(subject.to_json)
body = JSON.parse(serializer.to_json)
posts = body["post_stream"]["posts"]
expect(posts.count).to eq(1)
expect(posts.first["id"]).to eq(post.id)
expect(posts.first["reviewable_score_count"]).to eq(2)
expect(posts.first["reviewable_score_pending_count"]).to eq(1)
expect(body["post_stream"]["stream"]).to eq(nil)
expect(body["post_stream"]["timeline_lookup"]).to eq(nil)
expect(body["post_stream"]["gaps"]).to eq(nil)
end
end