質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.64%

Rails レスポンスタイムが遅い

解決済

回答 2

投稿

  • 評価
  • クリップ 0
  • VIEW 1,266

tomtom1

score 142

実現したいこと

こんにちは。
これまで、特に処理速度を気にせずに開発を進めていたら、Post/indexページのレスポンスタイムが4000msを超えていました。
実現したいことは、ページレスポンスを早くしたいです。
Post/indexでは、Post.allがeachされ、一覧で表示されます。
写真とともに、has_manyの関係にあるlike/favorite/commentの数が表示されています。

考えていること

色々と調べてみたら、下記のような原因を考えています。
・Userは、CACHEできているがlike/favorite/commentはされていないため、毎回読み込みを行ってしまっている。
・Indexを貼る(しかし、どのModelにどんなindexを張れば良いかわからないです..
Postに、t.index ["title"], name: "index_posts_on_title"
Postに、t.index ["image_name"], name: "index_posts_on_image_name"
なのか..)
・Railsビューに.countは使わない方がいい。

などもっと調べてらわかることも増えてくるとは思いますが、
現状知恵不足で、その解決方法や、果たして本当にそれが原因なのかを把握できずにおります。
宜しければ、ログを見る限りここを直した方がいい。などのご意見を頂ければ非常に助かります。

コード

<div class="main">
  <div class="container">
  <% @posts.each do |post| %>
    <h4><%= link_to(post.title, "/posts/#{post.id}") %></h4>
  </div>
  <div class="story-img">
    <%= link_to( image_tag(post.image_name.url), "/posts/#{post.id}") %>
  </div>
    <div class="story-icon">
    <% if Like.find_by(user_id: @current_user.id, post_id: post.id) %>
        <%= link_to("/likes/#{post.id}/destroy", {method: "post"}) do %>
          <span class="fa fa-heart icon-size like-btn-unlike"></span>
          <% end %>
      <% else %>
        <%= link_to("/likes/#{post.id}/create", {method: "post"}) do %>
        <span class="fa fa-heart icon-size like-btn"></span>
        <% end %>
      <% end %>
      <%= Like.where(post_id: post).count %>
      &emsp;
      <% if Favorite.find_by(user_id: @current_user.id, post_id: post.id) %>
        <%= link_to("/favorites/#{post.id}/destroy", {method: "post"}) do %>
          <span class="fa fa-star icon-size favorite-btn-unlike"></span>
        <% end %>
      <% else %>
        <%= link_to("/favorites/#{post.id}/create", {method: "post"}) do %>
          <span class="fa fa-star icon-size favorite-btn"></span>
        <% end %>
      <% end %>
      <%= Favorite.where(post_id: post).count %>
      &emsp;
      <% if Comment.find_by(user_id: @current_user.id, post_id: post.id) %>
          <span class="fa fa-comment icon-size comment-btn-unlike"></span>
      <% else %>
          <span class="fa fa-comment icon-size comment-btn"></span>
      <% end %>
      <%= Comment.where(post_id: post).count %>
    </div>
    <div class="memo">
      <%= link_to( image_tag(post.user.image_name.thumb.url), "/users/#{post.user.id}") if post.user.image_name.present? %>
      <%= link_to( image_tag("/user_images/default_user.jpg"), "/users/#{post.user.id}") if !post.user.image_name.present? %>
      <strong><%= link_to(post.user.name, "/users/#{post.user.id}") %></strong>
  <% end %>
  </div>
</div>
class Post < ApplicationRecord
  belongs_to :user
  has_many :likes, dependent: :delete_all
  has_many :favorites, dependent: :delete_all
  has_many :comments, dependent: :delete_all
end
def index
    @posts = Post.all.order(created_at: :desc)
    @post = Post.find_by(id: params[:id])
end
create_table "posts", force: :cascade do |t|
    t.string "title"
    t.text "content"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.integer "user_id"
    t.string "image_name"
  end

  create_table "users", force: :cascade do |t|
    t.string "name"
    t.string "email"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.string "image_name"
    t.string "password_digest"
  end


localhost上でログを確認。

Started GET "/posts/index" for 127.0.0.1 at 2019-12-10 17:48:43 +0900
Processing by PostsController#index as HTML
  Parameters: {"authenticity_token"=>"ODrhc0d544BUC1VlfJPYkI9hA7jaY1RvWEvO6GdWXKNWOWFffJVhFIDb4tiNAKrT1pQQyXLYtwa4GZ4QBSMzDA=="}
  User Load (0.6ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
  ↳ app/controllers/application_controller.rb:6
  Post Load (0.9ms)  SELECT  "posts".* FROM "posts" WHERE "posts"."id" IS NULL LIMIT ?  [["LIMIT", 1]]
  ↳ app/controllers/posts_controller.rb:28
  Rendering posts/index.html.erb within layouts/application
   (0.7ms)  SELECT COUNT(*) FROM "users" INNER JOIN "friendships" ON "users"."id" = "friendships"."followed_id" WHERE "friendships"."follower_id" = ?  [["follower_id", 1]]
  ↳ app/views/posts/index.html.erb:3
  Post Load (0.3ms)  SELECT "posts".* FROM "posts" ORDER BY "posts"."created_at" DESC
  ↳ app/views/posts/index.html.erb:10
  CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
  ↳ app/models/post.rb:19
  User Exists (0.2ms)  SELECT  1 AS one FROM "users" INNER JOIN "friendships" ON "users"."id" = "friendships"."followed_id" WHERE "friendships"."follower_id" = ? AND "users"."id" = ? LIMIT ?  [["follower_id", 1], ["id", 1], ["LIMIT", 1]]
  ↳ app/models/user.rb:32
  CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
  ↳ app/models/post.rb:19
  Like Load (0.6ms)  SELECT  "likes".* FROM "likes" WHERE "likes"."user_id" = ? AND "likes"."post_id" = ? LIMIT ?  [["user_id", 1], ["post_id", 5], ["LIMIT", 1]]
  ↳ app/views/posts/index.html.erb:28
   (0.2ms)  SELECT COUNT(*) FROM "likes" WHERE "likes"."post_id" = ?  [["post_id", 5]]
  ↳ app/views/posts/index.html.erb:37
  Favorite Load (0.6ms)  SELECT  "favorites".* FROM "favorites" WHERE "favorites"."user_id" = ? AND "favorites"."post_id" = ? LIMIT ?  [["user_id", 1], ["post_id", 5], ["LIMIT", 1]]
  ↳ app/views/posts/index.html.erb:39
   (0.2ms)  SELECT COUNT(*) FROM "favorites" WHERE "favorites"."post_id" = ?  [["post_id", 5]]
  ↳ app/views/posts/index.html.erb:48
  Comment Load (1.0ms)  SELECT  "comments".* FROM "comments" WHERE "comments"."user_id" = ? AND "comments"."post_id" = ? LIMIT ?  [["user_id", 1], ["post_id", 5], ["LIMIT", 1]]
  ↳ app/views/posts/index.html.erb:50
   (0.2ms)  SELECT COUNT(*) FROM "comments" WHERE "comments"."post_id" = ?  [["post_id", 5]]
  ↳ app/views/posts/index.html.erb:55
  CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
  ↳ app/models/post.rb:19
  CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
  ↳ app/models/post.rb:19
  CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
  ↳ app/models/post.rb:19
  CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
  ↳ app/models/post.rb:19
  CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
  ↳ app/models/post.rb:19
  CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
  ↳ app/models/post.rb:19
  CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
  ↳ app/models/post.rb:19
.
.
POSTの数だけ繰り返し
.
.
Rendered posts/index.html.erb within layouts/application (136.1ms)
Completed 200 OK in 398ms (Views: 277.1ms | ActiveRecord: 16.6ms)


実際には、Herokuにデプロイしたアプリのログだと、表示するPostも多いため、4135msもかかってしまうのが現状です。

Rendered posts/index.html.erb within layouts/application (4108.5ms)
Completed 200 OK in 4135ms (Views: 2349.3ms | ActiveRecord: 1775.3ms)


宜しくお願いします。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

checkベストアンサー

+1

問題点1 Posts.all

本番環境に適用する事を考えるとPost.allは件数が大きくなりすぎるのではないかと思います。
kaminariwill_paginateなどのpaginationライブラリを導入することを検討してみるといいかもしれません。

問題点2 View中でのfind_bywhere

View内でApplicationRecord.find_byApplicationRecord.whereを利用するのはViewの責務が曖昧になる問題があります。
さらに今回のようにループ中で用いる事でパフォーマンス的に劣化します。
基本的には関連付けを利用しましょう。

Like.find_by(user_id: @current_user.id, post_id: post.id) 

@current_user.likes.exists?(post_id: post.id)
これちょっと自信がないです。@current_user.likes.pluck(:post_id)から絞り込んだほうが速い気も…

class User < ApplicationRecord
  # 省略
  def like?(post)
    @like_posts ||= likes.pluck(:post_id)
    @like_posts.include?(post.id)
  end
end
<% if @current_user.like?(post) %>


ページねーとした場合likes.pluck(:post_id)ですとlikeを全件取得してしまうため
表示範囲に限定する工夫をした方がいいかもしれません。

Like.where(post_id: post).count

post.likes.count
N+1 countの問題が出ますので対処が必要です。

等など

問題点3 無駄な@post = Post.find_by(id: params[:id])

Controllerにて@post = Post.find_by(id: params[:id])を行っていますが
その後全く使っていないどころか、params[:id]nilのようで、どういった理由で何のデータを取ってきているのかわかりません。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/12/11 17:18

    ご丁寧にご回答頂き、ありがとうございます。
    【問題1】実はkaminariを導入しようとしているのですが、上手く行っておりません。
    もし宜しければ、こちらもご確認頂けないでしょうか。
    https://teratail.com/questions/227132

    【問題2】
    .find_by:解決致しました。ありがとうございます。
    .where.count:ControllerのPost.allにincludesを加える処理が必要ということでしょうか。現状、winterboumさんに回答しました状況で、AVOID eager loading detectedを受けています。

    【問題3】
    ありがとうございます。その通りなので、削除致しました。

    キャンセル

+1

これは 1+N 問題と言われているものです
@posts = Post.includes(:user).all.order(created_at: :desc)
として下さい。
この問題を検出してくれるtoolがあります。bullet で探して下さい

やり直し
post.user.name という参照があるのになぜ 不要と言われたのか、入れなくてもpopupしなかったのか、は疑問。
ここにないcodeでなにか有るのかもしれない。
で、
viewの中でLike.where とか Comment.find_by とかDBのアクセスが post毎に行われているのも原因です。
Post.includes にそれらを加えることと、場合によっては joinsして@current_user.id でのしぼりこみも行うという必要があるかも。
風邪気味なのであまりじっくり取り組めないので、上をヒントに工夫して下さい。
bulletにたどり着けるくらいの力があるから、大丈夫そう

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/12/10 23:39

    ご回答ありがとうございます!実はその可能性もないかと思い、bulletを導入し、検出していたものの、ポップアップは出てきませんでしたので、よくわからずにいました。

    また、今回.includes(:user)を追加しましたところ、下記の内容のポップアップが出てしまいました。
    AVOID eager loading detected
    Post => [:user]
    Remove from your finder: :include => [:user]
    Call stack
    なぜこのような警告がでるのでしょうか?ご指摘頂ければ幸いです。

    キャンセル

  • 2019/12/11 05:30 編集

    ああ、code見ずに書いてしまったので、そうなっちまった。
    post.user 以外にも問題有るのかも。やり直し

    キャンセル

  • 2019/12/11 17:37

    ここにないcodeでなにか有るのかもしれない。
    →まずはこちらの原因を探して、解決した後に、Like.whereのN+1問題をPost.includes にそれらを加えることと、場合によっては joinsして@current_user.id で解決していくという理解で宜しかったでしょうか?

    https://qa.atmarkit.co.jp/q/3369
    こちらのサイトのように、外部結合と内部結合の違いがあるかもしれないなど見ましたが、こういった原因は考えられますでしょうか?現状、ここにないcodeの中に原因があるかもしれないとのことで、色々と見直して見ましたが、特に関係性がありそうなコードは見つかっていません...。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.64%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る