ruby-on-rails 基础教程Rails应用加载视图非常慢

zysjyyx4  于 2023-02-01  发布在  Ruby
关注(0)|答案(1)|浏览(148)

我是Rails新手,刚刚读完Michael Hartl的优秀Rails教程,我正在开发另一个项目应用程序,它松散地遵循这个框架,但有不同之处。
我已经做了一部分,已经遇到了一个问题,我的应用程序的许多部分似乎异常缓慢,我不认为一个明显的原因。我把我的用户索引页的细节放在下面,因为这是最慢的。如下所示,它需要9.1秒加载,其中约8.7秒加载视图。这是在开发环境(AWS Cloud9),但即使部署在Heroku上,渲染数据库中只有13个用户的页面也需要~ 5秒!
5s在这个有限的规模下是相当糟糕的,并且rails教程示例应用程序在显示50个用户的情况下总共在447 ms内呈现用户索引页面!(运行在类似的AWS EC2示例上)我不认为我的应用程序有什么不同,足以导致加载时间增加20倍。
渲染/用户服务器输出:

Started GET "/users" for 24.85.170.222 at 2023-01-29 21:52:03 +0000
Cannot render console from 24.85.170.222! Allowed networks: 127.0.0.0/127.255.255.255, ::1
Processing by UsersController#index as HTML
  User Load (0.3ms)  SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 2], ["LIMIT", 1]]
  ↳ app/helpers/sessions_helper.rb:12:in `current_user'
  Rendering layout layouts/application.html.erb
  Rendering users/index.html.erb within layouts/application
  User Count (1.1ms)  SELECT COUNT(*) FROM "users"
  ↳ app/views/users/index.html.erb:5
  User Load (0.6ms)  SELECT "users".* FROM "users" LIMIT ? OFFSET ?  [["LIMIT", 30], ["OFFSET", 0]]
  ↳ app/views/users/index.html.erb:8
  Rendered users/index.html.erb within layouts/application (Duration: 7719.3ms | Allocations: 280179)
  Rendered layouts/_rails_default.html.erb (Duration: 7.6ms | Allocations: 6444)
  Rendered layouts/_shim.html.erb (Duration: 0.0ms | Allocations: 9)
  Company Load (0.1ms)  SELECT "companies".* FROM "companies" INNER JOIN "company_users" ON "companies"."id" = "company_users"."company_id" WHERE "company_users"."user_id" = ? AND "company_users"."employee" = ? LIMIT ?  [["user_id", 2], ["employee", 1], ["LIMIT", 1]]
  ↳ app/views/layouts/_header.html.erb:35
  Rendered layouts/_header.html.erb (Duration: 963.3ms | Allocations: 1473)
  Rendered layouts/_footer.html.erb (Duration: 0.2ms | Allocations: 73)
  Rendered layout layouts/application.html.erb (Duration: 8693.9ms | Allocations: 289996)
Completed 200 OK in 9147ms (Views: 8697.1ms | ActiveRecord: 2.2ms | Allocations: 291862)

用户_控制器.rb:

class UsersController < ApplicationController
  before_action :logged_in_user, only: [:index, :edit, :update, :destroy, :show]
  before_action :correct_user, only: [:edit, :update]

  def index
    @users = User.paginate(page: params[:page])
  end

end

/型号/用户.rb:

class User < ApplicationRecord

  before_save :downcase_email

  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i

  validates :email, presence: true, length: { maximum: 255 },
                    format: { with: VALID_EMAIL_REGEX },
                    uniqueness: true

  #Returns true if given token matches the digest
  def authenticated?(token)
    #debugger
    return false if session_digest.nil?
    BCrypt::Password.new(session_digest).is_password?(token)
  end

  # Returns the hash digest of the given string. 
  def User.digest(string)
    cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
                                                  BCrypt::Engine.cost
    BCrypt::Password.create(string, cost: cost)
  end

  #returns a session token to prevent session hijacking
  def set_session_token
    @session_token = SecureRandom.urlsafe_base64
    update_attribute(:session_digest, User.digest(@session_token))
    return @session_token
  end

  private

    def downcase_email
      email.downcase!
    end
end

会话_助手.rb:

module SessionsHelper

  def log_in(user)
    session[:user_id] = user.id
    #guard against session replay attacks
    session[:session_token] = user.set_session_token
  end

  def current_user
    if (user_id = session[:user_id])
      @sess_user ||= User.find_by(id: user_id)
      if @sess_user && @sess_user.authenticated?(session[:session_token])
        @current_user = @sess_user
      end
    end
  end

  def current_user?(user)
    user && user == current_user
  end

  def logged_in?
    !current_user.nil?
  end

  #confirms a logged in user
  def logged_in_user
    unless logged_in?
      store_location                    #store requested URL in a cookie for friendly forwarding
      flash[:danger] = "Please log in"
      redirect_to root_url, status: :see_other
    end
  end

end

/视图/用户/索引.html.erb:

<% provide(:title, 'All users') %>

<h1>All users</h1>

<%= will_paginate %>

<ul class="users">
  <% @users.each do |user| %>
    <li>
        <%= link_to user.display_name, user %>
        <% if current_user.superadmin? && !current_user?(user) %>
          | <%= link_to "delete", user, data: { "turbo-method": :delete, turbo_confirm: "Delete #{user.email}?" } %>
        <% end %>
    </li>
  <% end %>
</ul>

<%= will_paginate %>

视图/布局/应用程序.html.erb:

<!DOCTYPE html>
<html>
  <head>

    <title><%= full_title(yield(:title)) %></title>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <meta charset="utf-8">

    <%= render 'layouts/rails_default' %>
    <%= render 'layouts/shim' %>
    <%= javascript_importmap_tags %>
  </head>

  <body>
    <% flash.each do |message_type, message| %>
      <%= content_tag(:div, message, class: "alert alert-#{message_type}") %>
    <% end %>
        <%= render 'layouts/header' %>
    <div class="container">
      <%= yield %>
      <%= render 'layouts/footer' %>
      <%= debug(params) if Rails.env.development? %>
    </div>
  </body>
</html>

我的视图/布局/_header.html.erb文件也有几次命中is_logged_in?current_user
我使用的是与railstutorial.org示例应用程序相同的Gem,所以Gem不应该是问题所在。
一开始我以为current_user会根据日志多次访问数据库,所以我用@sess_user ||= User.find_by(id: user_id)行来记住它,但这几乎没有影响加载时间,当我与示例应用程序比较时,我意识到这不是因为数据库访问被缓存了:CACHE User Load (0.0ms)
我觉得有一些明显的东西我错过了。我已经阅读了N+1查询和使用.includes,特别是在我的网站上有一些其他的模型,这可能是相关的,但事实上,即使在非常普通的用户索引页面上也会出现这个问题,这让我感到困惑。
我仍然只是开发的一部分,但是我想在我走得太远和事情变得更复杂之前尝试和弄清楚这个问题。将非常感谢任何见解!

czq61nw1

czq61nw11#

Alex对这个问题的第一个评论直指问题的核心。我如下所示重构了current_user以记住返回值,这样每个视图只调用一次is_password?。即时缓解-页面加载时间现在低于400 ms。
这都归功于亚历克斯。他做到了。他的评论真的应该是“答案”,但我觉得我应该把这个贴在这里,这样问题就显示为“回答”了。

def current_user
    #memoize current user so authenicated? only runs once per page (its very slow @ 200ms ea)
    @current_user ||= begin
      if (user_id = session[:user_id])
        user ||= User.find_by(id: user_id)
        if user && user.authenticated?(session[:session_token])
          user
        end
      end
    end
  end

相关问题