PERF: Avoid calling expensive `PostGuardian#can_see_post?` multiple times.

Before

```
Your Results: (note for timings- percentile is first, duration is second
in millisecs)
---
topic_admin:
  50: 19
  75: 19
  90: 21
  99: 27
topic:
  50: 56
  75: 62
  90: 64
  99: 99
timings:
  load_rails: 1262
ruby-version: 2.4.1-p111
rss_kb: 198432
pss_kb: 136612
virtual: physical
architecture: amd64
operatingsystem: Ubuntu
memorysize: 15.59 GB
kernelversion: 4.10.0
physicalprocessorcount: 1
processor0: Intel(R) Core(TM) i7-6700K CPU @ 4.00GHz
rss_kb_9877: 327892
pss_kb_9877: 263671
rss_kb_9946: 325468
pss_kb_9946: 261671
rss_kb_10153: 326456
pss_kb_10153: 262657
```

After

```
Your Results: (note for timings- percentile is first, duration is second
in millisecs)
---
topic_admin:
  50: 18
  75: 18
  90: 20
  99: 28
topic:
  50: 41
  75: 42
  90: 46
  99: 49
timings:
  load_rails: 1201
ruby-version: 2.4.1-p111
rss_kb: 187936
pss_kb: 123596
virtual: physical
architecture: amd64
operatingsystem: Ubuntu
memorysize: 15.59 GB
kernelversion: 4.10.0
physicalprocessorcount: 1
processor0: Intel(R) Core(TM) i7-6700K CPU @ 4.00GHz
rss_kb_26478: 342360
pss_kb_26478: 276696
rss_kb_26547: 340368
pss_kb_26547: 275930
rss_kb_26747: 338964
pss_kb_26747: 274466
```
This commit is contained in:
Guo Xiang Tan 2017-09-08 13:07:22 +08:00
parent 164a388dcc
commit 5d4221fbe1
10 changed files with 52 additions and 32 deletions

View File

@ -13,8 +13,10 @@ class PostActionsController < ApplicationController
guardian.ensure_post_can_act!(
@post,
PostActionType.types[@post_action_type_id],
opts: {
is_warning: params[:is_warning],
taken_actions: taken
}
)
args = {}

View File

@ -250,7 +250,7 @@ module ApplicationHelper
end
def crawler_layout?
controller.try(:use_crawler_layout?)
controller&.use_crawler_layout?
end
def include_crawler_content?

View File

@ -247,7 +247,7 @@ class Topic < ActiveRecord::Base
def self.visible_post_types(viewed_by = nil)
types = Post.types
result = [types[:regular], types[:moderator_action], types[:small_action]]
result << types[:whisper] if viewed_by.try(:staff?)
result << types[:whisper] if viewed_by&.staff?
result
end

View File

@ -9,7 +9,7 @@ class BasicUserSerializer < ApplicationSerializer
if Hash === object
User.avatar_template(user[:username], user[:uploaded_avatar_id])
else
user.try(:avatar_template)
user&.avatar_template
end
end

View File

@ -108,15 +108,15 @@ class PostSerializer < BasicPostSerializer
end
def moderator?
!!(object.try(:user).try(:moderator?))
!!(object&.user&.moderator?)
end
def admin?
!!(object.try(:user).try(:admin?))
!!(object&.user&.admin?)
end
def staff?
!!(object.try(:user).try(:staff?))
!!(object&.user&.staff?)
end
def yours
@ -140,7 +140,7 @@ class PostSerializer < BasicPostSerializer
end
def display_username
object.user.try(:name)
object.user&.name
end
def primary_group_name
@ -154,15 +154,15 @@ class PostSerializer < BasicPostSerializer
end
def primary_group_flair_url
object.user.try(:primary_group).try(:flair_url)
object.user&.primary_group&.flair_url
end
def primary_group_flair_bg_color
object.user.try(:primary_group).try(:flair_bg_color)
object.user&.primary_group&.flair_bg_color
end
def primary_group_flair_color
object.user.try(:primary_group).try(:flair_color)
object.user&.primary_group&.flair_color
end
def link_counts
@ -189,11 +189,11 @@ class PostSerializer < BasicPostSerializer
end
def user_title
object.try(:user).try(:title)
object&.user&.title
end
def trust_level
object.try(:user).try(:trust_level)
object&.user&.trust_level
end
def reply_to_user
@ -225,14 +225,18 @@ class PostSerializer < BasicPostSerializer
# Summary of the actions taken on this post
def actions_summary
result = []
PostActionType.types.each do |sym, id|
next if [:bookmark].include?(sym)
can_see_post = scope.can_see_post?(object)
PostActionType.types.except(:bookmark).each do |sym, id|
count_col = "#{sym}_count".to_sym
count = object.send(count_col) if object.respond_to?(count_col)
summary = { id: id, count: count }
summary[:hidden] = true if sym == :vote
summary[:can_act] = true if scope.post_can_act?(object, sym, taken_actions: actions)
if scope.post_can_act?(object, sym, opts: { taken_actions: actions }, can_see_post: can_see_post)
summary[:can_act] = true
end
if sym == :notify_user && scope.current_user.present? && scope.current_user == object.user
summary.delete(:can_act)
@ -276,7 +280,7 @@ class PostSerializer < BasicPostSerializer
end
def include_raw?
@add_raw.present? && (!object.hidden || scope.user.try(:staff?) || yours)
@add_raw.present? && (!object.hidden || scope.user&.staff? || yours)
end
def include_link_counts?
@ -326,7 +330,7 @@ class PostSerializer < BasicPostSerializer
end
def is_auto_generated
object.incoming_email.try(:is_auto_generated)
object.incoming_email&.is_auto_generated
end
def include_is_auto_generated?

View File

@ -83,7 +83,7 @@ class TopicViewSerializer < ApplicationSerializer
result[:allowed_users] = object.topic.allowed_users.select do |user|
!allowed_user_ids.include?(user.id)
end.map do |user|
end.map! do |user|
BasicUserSerializer.new(user, scope: scope, root: false)
end
end
@ -94,7 +94,7 @@ class TopicViewSerializer < ApplicationSerializer
end
end
if object.suggested_topics.try(:topics).present?
if object.suggested_topics&.topics.present?
result[:suggested_topics] = object.suggested_topics.topics.map do |t|
SuggestedTopicSerializer.new(t, scope: scope, root: false)
end
@ -215,7 +215,7 @@ class TopicViewSerializer < ApplicationSerializer
def actions_summary
result = []
return [] unless post = object.posts.try(:first)
return [] unless post = object.posts&.first
PostActionType.topic_flag_types.each do |sym, id|
result << { id: id,
count: 0,

View File

@ -3,9 +3,8 @@ module PostGuardian
# Can the user act on the post in a particular way.
# taken_actions = the list of actions the user has already taken
def post_can_act?(post, action_key, opts = {})
return false unless can_see_post?(post)
def post_can_act?(post, action_key, opts: {}, can_see_post: nil)
return false unless (can_see_post.nil? && can_see_post?(post)) || can_see_post
# no warnings except for staff
return false if (action_key == :notify_user && !is_staff? && opts[:is_warning].present? && opts[:is_warning] == 'true')
@ -187,7 +186,7 @@ module PostGuardian
end
def can_vote?(post, opts = {})
post_can_act?(post, :vote, opts)
post_can_act?(post, :vote, opts: opts)
end
def can_change_post_owner?

View File

@ -7,7 +7,10 @@ class PostActionCreator
end
def perform(action)
guardian.ensure_post_can_act!(@post, action, taken_actions: PostAction.counts_for([@post].compact, @user)[@post.try(:id)])
guardian.ensure_post_can_act!(@post, action, opts: {
taken_actions: PostAction.counts_for([@post].compact, @user)[@post&.id]
})
PostAction.act(@user, @post, action)
end

View File

@ -57,11 +57,15 @@ describe Guardian do
end
it "returns false when you've already done it" do
expect(Guardian.new(user).post_can_act?(post, :like, taken_actions: { PostActionType.types[:like] => 1 })).to be_falsey
expect(Guardian.new(user).post_can_act?(post, :like, opts: {
taken_actions: { PostActionType.types[:like] => 1 }
})).to be_falsey
end
it "returns false when you already flagged a post" do
expect(Guardian.new(user).post_can_act?(post, :off_topic, taken_actions: { PostActionType.types[:spam] => 1 })).to be_falsey
expect(Guardian.new(user).post_can_act?(post, :off_topic, opts: {
taken_actions: { PostActionType.types[:spam] => 1 }
})).to be_falsey
end
it "returns false for notify_user if private messages are disabled" do
@ -863,11 +867,15 @@ describe Guardian do
end
it "doesn't allow voting if the user has an action from voting already" do
expect(guardian.post_can_act?(post, :vote, taken_actions: { PostActionType.types[:vote] => 1 })).to be_falsey
expect(guardian.post_can_act?(post, :vote, opts: {
taken_actions: { PostActionType.types[:vote] => 1 }
})).to be_falsey
end
it "allows voting if the user has performed a different action" do
expect(guardian.post_can_act?(post, :vote, taken_actions: { PostActionType.types[:like] => 1 })).to be_truthy
expect(guardian.post_can_act?(post, :vote, opts: {
taken_actions: { PostActionType.types[:like] => 1 }
})).to be_truthy
end
it "isn't allowed on archived topics" do

View File

@ -53,7 +53,11 @@ describe PostActionsController do
it "passes a list of taken actions through" do
PostAction.create(post_id: @post.id, user_id: @user.id, post_action_type_id: PostActionType.types[:inappropriate])
Guardian.any_instance.expects(:post_can_act?).with(@post, :off_topic, has_entry(taken_actions: has_key(PostActionType.types[:inappropriate])))
Guardian.any_instance.expects(:post_can_act?).with(@post, :off_topic,
has_entry(opts: has_entry(taken_actions: has_key(PostActionType.types[:inappropriate])))
)
xhr :post, :create, id: @post.id, post_action_type_id: PostActionType.types[:off_topic]
end