Поиск / устранение проблемы с памятью в конкретном контроллере в rails - PullRequest
0 голосов
/ 29 октября 2018

У меня есть сайт, построенный на Rails, на Heroku, который, как правило, работает нормально, используя около 90% памяти.

Через Scout я выделил проблему в своем Rails-приложении, где мои комментарии # create-controller иногда выделяют 860 КБ памяти, что приводит к выключению моего приложения на долгое время в течение следующих тайм-аутов и т. Д. Большую часть времени выделенная память является частью этого, поэтому проблема периодически.

Функция комментария сама по себе не супер важна, но она мне все еще нужна. Я полагаю, что три его части могут вызвать проблемы с памятью:

  1. Слишком длинная строка содержимого (то есть сам комментарий). Например, если спамер публикует супер длинный текст. Я не верю, что это проблема, поскольку мой последний всплеск памяти был вызван обычным пользователем, который оставил очень короткий комментарий.

  2. Мой rakismet-gem (https://github.com/joshfrench/rakismet) и проверка на спам. Я использую последнюю версию (1.5.4). Вполне вероятно, что это проблема, поскольку я действительно не знаю, что загружается в память при использовании.

  3. Мой уведомитель-звонок в коде.

    • Могу ли я что-нибудь сделать, чтобы выявить проблемы с памятью и спасти в контроллере, поэтому, если есть какие-либо "плохие" комментарии, они не нарушат весь сайт?

    • Видите ли вы что-нибудь, что может вызвать выделение памяти монстра в коде?

Код ниже:

Комментарии # Создать:

  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

Ответы [ 2 ]

0 голосов
/ 29 октября 2018

одна вещь, которая выделяется для меня - это ваш газон еще заявление

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
0 голосов
/ 29 октября 2018

Выдающаяся проблема заключается в следующем:

Notifier.new_comment(@comment).deliver if Rails.env == 'production' unless @comment.marked_as_spam

Я предполагаю, что это объект ActionMailer. deliver - это метод блокировки, а не тот, который вы обычно хотите использовать в производственном процессе в течение цикла запрос-ответ. Это может привести к большим задержкам, если ваш почтовый сервер медленно реагирует, поэтому вам следует заменить его на delivery_later и убедиться, что у вас есть инструмент, такой как Sidekiq, для выполнения запроса в фоновом режиме.

(deliver устарело с Rails 5 между прочим, в пользу deliver_now и deliver_later.)

...