Finding/killing rails 中特定控制器的内存问题
Finding/killing a memory problem in a specific controller in rails
我在 Heroku 上的 Rails 上建立了一个网站,该网站通常在大约 90% 的内存使用情况下运行良好。
通过 Scout,我在我的 Rails-app 中发现了一个问题,我的 comments#create-controller 有时会分配 860k 内存,这会导致我的应用在超时等情况下长时间关闭.大多数情况下,分配的内存只是其中的一小部分,因此问题是间歇性的。
评论功能本身不是特别重要,但我仍然需要它。我相信它的三个不同部分可能会导致此内存问题:
内容字符串(即评论本身)太长。例如,如果垃圾邮件发送者发布超长文本。我认为这不是问题,因为我上次的内存峰值是由普通用户引起的,他发表了一条非常简短的评论。
My rakismet-gem (https://github.com/joshfrench/rakismet) 和垃圾邮件检查。我使用的是最新版本 (1.5.4)。这可能是个问题,因为我真的不知道在使用内存时加载到内存中的内容。
代码中我的通知程序调用。
我能做些什么来捕捉内存问题并在控制器中进行救援,这样如果有任何 "bad" 评论,它们就不会破坏整个站点?
您在代码中看到任何可能导致此怪物内存分配的内容吗?
代码如下:
评论#创建:
def create
require 'memory_profiler'
report = MemoryProfiler.report do
@comment = Comment.new(comment_params)
spam_features = %w(\xA cialis informative the that this buy href)
unless @current_administrator.present?
if spam_features.any? {|str| @comment.content.include? str}
logger.info "L: Comment include spam features"
redirect_to article_path(Article.find('din-kommentar-har-inte-sparats')) and return
elsif @comment.author.size > 40 || @comment.author_email.size > 40
logger.info "L: Comment author name or email too long (suspicious)"
redirect_to article_path(Article.find('din-kommentar-har-inte-sparats')) and return
end
end
# This shouldn't be here (but don't know how to put it in the model)
if !@comment.blog_post_id.blank? # This is a comment on a blog post
return_to_path = blog_post_path(BlogPost.find(@comment.blog_post_id))
elsif !@comment.gift_id.blank? # This is a comment on a gift
return_to_path = gift_path(Gift.find(@comment.gift_id))
elsif !@comment.contest_id.blank? # This is a comment on a contest
return_to_path = contest_path(Contest.find(@comment.contest_id))
elsif !@comment.christmas_fair_id.blank? # This is a comment on a christmas fair
return_to_path = christmas_fair_path(ChristmasFair.find(@comment.christmas_fair_id))
elsif @comment.tmp_julrim # This is a comment on a christmas fair
return_to_path = rhymes_path
else
raise ActionController::RoutingError.new('Not Found')
end
return_to_path << "#comments"
@comment.status_id = 3
@comment.user_ip = request.remote_ip
@comment.user_agent = request.env['HTTP_USER_AGENT']
@comment.marked_as_spam = @comment.spam? # Using rakismet to check for spam
#if !@comment.marked_as_spam || @current_administrator.present?
respond_to do |format|
#@comment.status_id = 1 if @comment.contest_id == 44
if @comment.save
Notifier.new_comment(@comment).deliver if Rails.env == 'production' unless @comment.marked_as_spam
format.html { redirect_to return_to_path, notice: 'Din kommentar har registrerats och kommer att ses över innan den godkänns.' }
# format.json { render action: 'show', status: :created, location: @comment }
else
format.html { render action: 'new' }
format.json { render json: @comment.errors, status: :unprocessable_entity }
end
end
end
突出的问题是:
Notifier.new_comment(@comment).deliver if Rails.env == 'production' unless @comment.marked_as_spam
我假设这是一个 ActionMailer 对象。 deliver
是一种阻塞方法,而不是您通常希望在请求-响应周期中在生产中使用的方法。如果您的邮件服务器响应缓慢,这可能会导致严重延迟,因此您应该将其替换为 deliver_later 并确保您有像 Sidekiq 这样的工具可以在后台完成请求。
(deliver
自 Rails 5 顺便说一句,已弃用,取而代之的是 deliver_now
和 deliver_later
。)
让我印象深刻的一件事是你的地盘 else 声明
raise ActionController::RoutingError.new('Not Found')
加薪。只需在此处渲染 401。您已经知道它是一个 401,可以避免通过堆栈加注。也可以将整个逻辑移至专用的受保护方法。以下是我将如何用评论重构你的方法。
# always do requires in the file before the class definition
# so this would go at the top of the file
require 'memory_profiler'
...
def create
report = MemoryProfiler.report do
@comment = Comment.new(comment_params)
check_admin?
# There is possibility to merge these with the comment params above
# during init above or just pass them to the model and act upon
# appropriately there
@comment.status_id = 3
@comment.user_ip = request.remote_ip
@comment.user_agent = request.env['HTTP_USER_AGENT']
@comment.marked_as_spam = @comment.spam? # Using rakismet to check for spam
#if !@comment.marked_as_spam || @current_administrator.present?
respond_to do |format|
if @comment.save
Notifier.new_comment(@comment).deliver if Rails.env.production? && !@comment.marked_as_spam
format.html {
if return_to_path == false
render file: "public/401.html", status: :not_found # dump to 401 immediately
else
redirect_to return_to_path, notice: 'Din kommentar har registrerats och kommer att ses över innan den godkänns.'
end
}
# format.json { render action: 'show', status: :created, location: @comment }
else
format.html { render action: 'new' }
format.json { render json: @comment.errors, status: :unprocessable_entity }
end
end
end
end
protected
def spam_features
%w(\xA cialis informative the that this buy href)
end
def return_to_path
anchor = "comments"
if @comment.blog_post_id.present?
blog_post_path(@comment.blog_post, anchor: anchor) # trust your associations vs. relookup and leverage the anchor option in url helpers
elsif @comment.gift_id.present?
gift_path(@comment.gift, anchor: anchor) # trust your associations vs. relookup and leverage the anchor option in url helpers
elsif @comment.contest_id.present?
contest_path(@comment.contest, anchor: anchor) # trust your associations vs. relookup and leverage the anchor option in url helpers
elsif @comment.christmas_fair_id.present?
christmas_fair_path(@comment.christmas_fair, anchor: anchor) # trust your associations vs. relookup and leverage the anchor option in url helpers
elsif @comment.tmp_julrim
rhymes_path(anchor: "comments") and leverage the anchor option in url helpers
else
false # give a testable exit condition and for short circut render
end
end
# if you were to check the comment_params vs an instantiated object, you could
# short circuit the controller method in a before_action
# Also check out known existing methods of spam prevention such as invisible_captcha or rack attack. Ideally
# once you hit your controller's method spam checking is done.
def check_admin?
# for clarity use positive logic check when possible, e.g. if blank? vs unless present?
# reduce your guard code to one the fewest levels necessary and break out into testable methods
if has_spam?
logger.info {"L: Comment include spam features"} # use blocks for lazy evaluation of logger
redirect_to article_path(Article.find('din-kommentar-har-inte-sparats')) and return
elsif has_suspicious_name?
logger.info {"L: Comment author name or email too long (suspicious)"} # use blocks for lazy evaluation of logger
redirect_to article_path(Article.find('din-kommentar-har-inte-sparats')) and return
end
# is there be an else condition here that we're not accounting for here?
end
# this check is less than optimal, e.g. use of any? and include? has code smell
def has_spam?
@current_administrator.blank? && spam_features.any? {|str| @comment.content.include? str }
end
def has_suspicious_name?
@current_administrator.blank? && @comment.author.size > 40 || @comment.author_email.size > 40
end
我在 Heroku 上的 Rails 上建立了一个网站,该网站通常在大约 90% 的内存使用情况下运行良好。
通过 Scout,我在我的 Rails-app 中发现了一个问题,我的 comments#create-controller 有时会分配 860k 内存,这会导致我的应用在超时等情况下长时间关闭.大多数情况下,分配的内存只是其中的一小部分,因此问题是间歇性的。
评论功能本身不是特别重要,但我仍然需要它。我相信它的三个不同部分可能会导致此内存问题:
内容字符串(即评论本身)太长。例如,如果垃圾邮件发送者发布超长文本。我认为这不是问题,因为我上次的内存峰值是由普通用户引起的,他发表了一条非常简短的评论。
My rakismet-gem (https://github.com/joshfrench/rakismet) 和垃圾邮件检查。我使用的是最新版本 (1.5.4)。这可能是个问题,因为我真的不知道在使用内存时加载到内存中的内容。
代码中我的通知程序调用。
我能做些什么来捕捉内存问题并在控制器中进行救援,这样如果有任何 "bad" 评论,它们就不会破坏整个站点?
您在代码中看到任何可能导致此怪物内存分配的内容吗?
代码如下:
评论#创建:
def create
require 'memory_profiler'
report = MemoryProfiler.report do
@comment = Comment.new(comment_params)
spam_features = %w(\xA cialis informative the that this buy href)
unless @current_administrator.present?
if spam_features.any? {|str| @comment.content.include? str}
logger.info "L: Comment include spam features"
redirect_to article_path(Article.find('din-kommentar-har-inte-sparats')) and return
elsif @comment.author.size > 40 || @comment.author_email.size > 40
logger.info "L: Comment author name or email too long (suspicious)"
redirect_to article_path(Article.find('din-kommentar-har-inte-sparats')) and return
end
end
# This shouldn't be here (but don't know how to put it in the model)
if !@comment.blog_post_id.blank? # This is a comment on a blog post
return_to_path = blog_post_path(BlogPost.find(@comment.blog_post_id))
elsif !@comment.gift_id.blank? # This is a comment on a gift
return_to_path = gift_path(Gift.find(@comment.gift_id))
elsif !@comment.contest_id.blank? # This is a comment on a contest
return_to_path = contest_path(Contest.find(@comment.contest_id))
elsif !@comment.christmas_fair_id.blank? # This is a comment on a christmas fair
return_to_path = christmas_fair_path(ChristmasFair.find(@comment.christmas_fair_id))
elsif @comment.tmp_julrim # This is a comment on a christmas fair
return_to_path = rhymes_path
else
raise ActionController::RoutingError.new('Not Found')
end
return_to_path << "#comments"
@comment.status_id = 3
@comment.user_ip = request.remote_ip
@comment.user_agent = request.env['HTTP_USER_AGENT']
@comment.marked_as_spam = @comment.spam? # Using rakismet to check for spam
#if !@comment.marked_as_spam || @current_administrator.present?
respond_to do |format|
#@comment.status_id = 1 if @comment.contest_id == 44
if @comment.save
Notifier.new_comment(@comment).deliver if Rails.env == 'production' unless @comment.marked_as_spam
format.html { redirect_to return_to_path, notice: 'Din kommentar har registrerats och kommer att ses över innan den godkänns.' }
# format.json { render action: 'show', status: :created, location: @comment }
else
format.html { render action: 'new' }
format.json { render json: @comment.errors, status: :unprocessable_entity }
end
end
end
突出的问题是:
Notifier.new_comment(@comment).deliver if Rails.env == 'production' unless @comment.marked_as_spam
我假设这是一个 ActionMailer 对象。 deliver
是一种阻塞方法,而不是您通常希望在请求-响应周期中在生产中使用的方法。如果您的邮件服务器响应缓慢,这可能会导致严重延迟,因此您应该将其替换为 deliver_later 并确保您有像 Sidekiq 这样的工具可以在后台完成请求。
(deliver
自 Rails 5 顺便说一句,已弃用,取而代之的是 deliver_now
和 deliver_later
。)
让我印象深刻的一件事是你的地盘 else 声明
raise ActionController::RoutingError.new('Not Found')
加薪。只需在此处渲染 401。您已经知道它是一个 401,可以避免通过堆栈加注。也可以将整个逻辑移至专用的受保护方法。以下是我将如何用评论重构你的方法。
# always do requires in the file before the class definition
# so this would go at the top of the file
require 'memory_profiler'
...
def create
report = MemoryProfiler.report do
@comment = Comment.new(comment_params)
check_admin?
# There is possibility to merge these with the comment params above
# during init above or just pass them to the model and act upon
# appropriately there
@comment.status_id = 3
@comment.user_ip = request.remote_ip
@comment.user_agent = request.env['HTTP_USER_AGENT']
@comment.marked_as_spam = @comment.spam? # Using rakismet to check for spam
#if !@comment.marked_as_spam || @current_administrator.present?
respond_to do |format|
if @comment.save
Notifier.new_comment(@comment).deliver if Rails.env.production? && !@comment.marked_as_spam
format.html {
if return_to_path == false
render file: "public/401.html", status: :not_found # dump to 401 immediately
else
redirect_to return_to_path, notice: 'Din kommentar har registrerats och kommer att ses över innan den godkänns.'
end
}
# format.json { render action: 'show', status: :created, location: @comment }
else
format.html { render action: 'new' }
format.json { render json: @comment.errors, status: :unprocessable_entity }
end
end
end
end
protected
def spam_features
%w(\xA cialis informative the that this buy href)
end
def return_to_path
anchor = "comments"
if @comment.blog_post_id.present?
blog_post_path(@comment.blog_post, anchor: anchor) # trust your associations vs. relookup and leverage the anchor option in url helpers
elsif @comment.gift_id.present?
gift_path(@comment.gift, anchor: anchor) # trust your associations vs. relookup and leverage the anchor option in url helpers
elsif @comment.contest_id.present?
contest_path(@comment.contest, anchor: anchor) # trust your associations vs. relookup and leverage the anchor option in url helpers
elsif @comment.christmas_fair_id.present?
christmas_fair_path(@comment.christmas_fair, anchor: anchor) # trust your associations vs. relookup and leverage the anchor option in url helpers
elsif @comment.tmp_julrim
rhymes_path(anchor: "comments") and leverage the anchor option in url helpers
else
false # give a testable exit condition and for short circut render
end
end
# if you were to check the comment_params vs an instantiated object, you could
# short circuit the controller method in a before_action
# Also check out known existing methods of spam prevention such as invisible_captcha or rack attack. Ideally
# once you hit your controller's method spam checking is done.
def check_admin?
# for clarity use positive logic check when possible, e.g. if blank? vs unless present?
# reduce your guard code to one the fewest levels necessary and break out into testable methods
if has_spam?
logger.info {"L: Comment include spam features"} # use blocks for lazy evaluation of logger
redirect_to article_path(Article.find('din-kommentar-har-inte-sparats')) and return
elsif has_suspicious_name?
logger.info {"L: Comment author name or email too long (suspicious)"} # use blocks for lazy evaluation of logger
redirect_to article_path(Article.find('din-kommentar-har-inte-sparats')) and return
end
# is there be an else condition here that we're not accounting for here?
end
# this check is less than optimal, e.g. use of any? and include? has code smell
def has_spam?
@current_administrator.blank? && spam_features.any? {|str| @comment.content.include? str }
end
def has_suspicious_name?
@current_administrator.blank? && @comment.author.size > 40 || @comment.author_email.size > 40
end