Ruby
 Computer >> コンピューター >  >> プログラミング >> Ruby

Railsでのビューキャッシングについて知りたいことすべて

キャッシングとは、コードの結果を保存して、後ですばやく取得できるようにすることを意味する一般的な用語です。これにより、たとえば、データベースに何度もアクセスして、めったに変更されないデータを取得することを回避できます。一般的な概念はすべてのタイプのキャッシングで同じですが、Railsは、キャッシュしようとしているものに応じてさまざまな支援を提供します。

Rails開発者の場合、キャッシングの一般的な形式には、メモ化、低レベルのキャッシング(両方ともこのキャッシングシリーズの前の部分で説明)、およびビューキャッシングが含まれます。これらについてはここで説明します。

RubyonRailsがビューをレンダリングする方法

まず、少し紛らわしい用語について説明します。 Railsコミュニティが「ビュー」と呼んでいるのは、app/views内にあるファイルです。 ディレクトリ。通常、これらは.html.erbです。 他のオプション(つまり、プレーンな.html)がありますが、ファイル 、.js.erb 、またはslimやhamlなどの他のプリプロセッサを使用するファイル)。他の多くのWebフレームワークでは、これらのファイルは「テンプレート」と呼ばれ、それらの使用法をより適切に説明していると思います。

RailsアプリケーションがGETを受信したとき リクエストの場合、特定のコントローラーアクション(UsersController#indexなど)にルーティングされます。 。次に、アクションは、データベースから必要な情報を収集し、ビュー/テンプレートファイルのレンダリングで使用するためにそれを渡す責任があります。この時点で、「ビューレイヤー」に入ります。

通常、ビュー(またはテンプレート)は、ハードコードされたHTMLマークアップと動的なRubyコードを組み合わせたものになります。

#app/views/users/index.html.erb

<div class='user-list'>
  <% @users.each do |user| %>
    <div class='user-name'><%= user.name %></div>
  <% end %>
</div>

ビューをレンダリングするには、ファイル内のRubyコードを実行する必要があります(erbの場合) それは<% %>のすべてです タグ)。ページを100回更新し、@users.each... 100回実行されます。含まれているパーシャルについても同じことが言えます。プロセッサは部分的なhtml.erbをロードする必要があります ファイルを作成し、その中のすべてのRubyコードを実行し、結果を1つのHTMLファイルに結合して、リクエスターに送り返します。

ビューが遅くなる原因

開発中にページを表示すると、Railsは次のような多くのログ情報を出力することに気付いたと思います。

Processing by PagesController#home as HTML
  Rendering layouts/application.html.erb
  Rendering pages/home.html.erb within layouts/application
  Rendered pages/home.html.erb within layouts/application (Duration: 4.0ms | Allocations: 1169)
  Rendered layouts/application.html.erb (Duration: 35.9ms | Allocations: 8587)
Completed 200 OK in 68ms (Views: 40.0ms | ActiveRecord: 15.7ms | Allocations: 14307)

この段階では、最後の行が最も役立ちます。左から右に時間をたどると、Railsがブラウザに応答を返すのにかかった合計時間は68ミリ秒であり、そのうち40ミリ秒がerbのレンダリングに費やされたことがわかります。 ファイルとActiveRecordクエリの処理に15.7ミリ秒。

これは簡単な例ですが、ビューレイヤーのキャッシュを検討する理由も示しています。 ActiveRecordクエリを魔法のように瞬時に実行できたとしても、erbのレンダリングに2倍以上の時間を費やしています。 。

ビューのレンダリングが遅くなる理由はいくつかあります。たとえば、ビュー内で高価なDBクエリを呼び出したり、ループ内で多くの作業を実行したりする場合があります。私が見た中で最も一般的な状況の1つは、おそらく複数のレベルのネストを使用して、単純に多くのパーシャルをレンダリングすることです。

個々の行を処理するパーシャルがある可能性があるメールの受信トレイを想像してみてください:

# app/views/emails/_email.html.erb

<li class="email-line">
  <div class="email-sender">
    <%= email.from_address %>
  </div>
  <div class="email-subject">
    <%= email.subject %>
  </div>
</div>

また、メインの受信トレイページで、各メールの部分をレンダリングします。

# app/views/emails/index.html.erb

...
<% @emails.each do |email| %>
  <%= render email %>
<% end %>

受信トレイに100個のメッセージがある場合、_email.html.erbをレンダリングしています。 パーシャル100回。ささいな例では、これはそれほど問題ではありません。私のマシンでは、部分的にインデックス全体をレンダリングするのに15ミリ秒しかかかりません。もちろん、実際の例はもっと複雑で、他の部分が含まれている場合もあります。レンダリング時間が長くなることは難しくありません。 _emailのレンダリングに1〜2ミリ秒しかかからない場合でも 部分的には、コレクション全体を実行するのに100〜200ミリ秒かかります。

幸い、Railsには、__emailだけをキャッシュするかどうかに関係なく、この問題を解決するためにキャッシュを簡単に追加できる機能が組み込まれています。 部分的、index ページ、またはその両方。

ビューキャッシュとは

Ruby on Railsのビューキャッシングは、ビューが生成するHTMLを取得し、後で使用できるように保存します。 Railsはこれらをファイルシステムに書き込んだり、メモリに保持したりすることをサポートしていますが、本番環境で使用する場合は、MemcachedやRedisなどのスタンドアロンのキャッシュサーバーが必要になることはほぼ間違いありません。 Railsのmemory_store 開発には役立ちますが、プロセス間で共有することはできません(たとえば、複数のサーバー/ダイノまたはユニコーンなどのフォークサーバー)。同様に、file_store サーバーに対してローカルです。したがって、複数のボックス間で共有することはできず、期限切れのエントリは自動的に削除されないため、定期的にRails.cache.clearを呼び出す必要があります。 サーバーのディスクがいっぱいになるのを防ぐためです。

キャッシュストアの有効化は、環境構成ファイル(config/environments/production.rbなど)で実行できます。 ):

  # memory store is handy for testing
  # during development but not advisable
  # for production
  config.cache_store = :memory_store

デフォルトのインストールでは、development.rb マシンのキャッシュを簡単に切り替えることができるように、すでにいくつかの構成が行われています。 rails dev:cacheを実行するだけです キャッシュのオンとオフを切り替えます。

Railsでビューをキャッシュするのは一見簡単なので、パフォーマンスの違いを説明するために、sleep(5)を使用します。 人工的な遅延を作成するには:

<% cache do %>
  <div>
    <p>Hi <%= @user.name %>
    <% sleep(5) %>
  </div>
<% end %>

このビューを最初にレンダリングするには、予想どおり5秒かかります。ただし、cache do内のすべてが実行するため、2回目のロードには数ミリ秒しかかかりません。 ブロックはキャッシュからフェッチされます。

例によるビューキャッシュの追加

小さな例を見て、キャッシュのオプションを見ていきましょう。このビューが実際にいくつかのパフォーマンスの問題を引き起こしていると想定します:

# app/views/user/show.html.erb
<div>
  Hi <%= @user.name %>!
<div>

<div>
  Here's your list of posts,
  you've written
  <%= @user.posts.count %> so far
  <% @user.posts.each do |post|
    <div><%= post.body %></div>
  <% end %>
</div>

<% sleep(5) #artificial delay %>

これにより、作業する基本的なスケルトンと、人工的な5秒の遅延が得られます。まず、show.html.erb全体をラップできます cache do内のファイル 前に説明したように、ブロックします。これで、キャッシュがウォームになると、レンダリング時間が短縮されます。ただし、この計画で問題が発生し始めるのにそれほど時間はかかりません。

まず、ユーザーが名前を変更するとどうなりますか?キャッシュされたページをいつ期限切れにするかをRailsに通知していないため、ユーザーに更新されたバージョンが表示されない場合があります。簡単な解決策は、@userを渡すだけです。 cacheへのオブジェクト 方法:

<% cache(@user) do %>
<div>
  Hi <%= @user.name %>!
</div>
...
<% sleep(5) #artificial delay %>
<% end %>

低レベルのキャッシュに関するこのシリーズの前回の記事では、キャッシュキーの詳細について説明したので、ここでは再度説明しません。今のところ、モデルをcache()に渡すと十分です。 、そのモデルのupdated_atを使用します キャッシュで検索するキーを生成する属性。つまり、@user が更新され、このキャッシュされたページは期限切れになり、RailsはHTMLを再レンダリングします。

ユーザーが名前を変更した場合は対応しましたが、投稿についてはどうでしょうか。既存の投稿を変更したり、新しい投稿を作成したりしても、updated_atは変更されません。 Userのタイムスタンプ 、したがって、キャッシュされたページは期限切れになりません。さらに、ユーザーが名前を変更した場合、すべてを再レンダリングします 彼らの投稿が変更されていなくても、彼らの投稿の。これらの両方の問題を解決するため。 「ロシアの人形のキャッシュ」(つまり、キャッシュ内のキャッシュ)を使用できます:

<% cache(@user) do %>
  <div>
    Hi <%= @user.name %>!
  <div>

  <div>
    Here's your list of posts,
    you've written
    <%= @user.posts.count %> so far<br>
    <% @user.posts.each do |post| %>
      <% cache(post) do %>
        <div><%= post.body %></div>
      <% end %>
    <% end %>
  </div>

  <% sleep(5) #artificial delay %>
<% end %>

現在、個別にレンダリングされた各postをキャッシュしています (現実の世界では、これはおそらく部分的なものです)。したがって、@user が更新された場合、投稿を再レンダリングする必要はありません。キャッシュされた値を使用できます。ただし、まだもう1つ問題があります。 postの場合 が変更されても、@user.update_atのため、更新はレンダリングされません。 変更されていないため、cache(@user) do 実行されません。

この問題を修正するには、touch: trueを追加する必要があります Postへ モデル:

class Post < ApplicationRecord
  belongs_to :user, touch: true
end

touch: trueを追加する ここでは、ActiveRecordに毎回 投稿が更新されました。updated_atが必要です 「所属する」ユーザーのタイムスタンプも更新されます。

また、Railsは、その一般性を考慮して、パーシャルのコレクションをレンダリングするための特定のヘルパーを提供することも追加する必要があります。

  <%= render partial: 'posts/post',
       collection: @posts, cached: true %>

これは機能的に次のものと同等です:

<% @posts.each do |post| %>
  <% cache(post) do %>
    <%= render post %>
  <% end %>
<% end %>

render partial: ... cached: trueだけではありません Railsは、コレクション内の各アイテムのキャッシュストアにアクセスするのではなく、キャッシュストアにマルチゲットを発行できるため(つまり、1回のラウンドトリップで多くのキーと値のペアを読み取ることができるため)、冗長性が低くなります。

動的ページコンテンツ

一部のページには、周囲のページの他の部分よりもはるかに速い速度で変化する「動的な」コンテンツがある程度含まれているのが一般的です。これは、アクティビティ/ニュースフィードがある可能性があるホームページまたはダッシュボードに特に当てはまります。キャッシュされたページにこれらを含めると、キャッシュを頻繁に無効にする必要があり、そもそもキャッシュから得られるメリットが制限される可能性があります。

簡単な例として、現在の日をビューに追加しましょう:

<% cache(@user) do %>
  <div>
    Hi <%= @user.name %>,
    hope you're having a great
    <%= Date.today.strftime("%A") %>!
  <div>

  ...
<% end %>

キャッシュを毎日無効にすることはできますが、明らかな理由から、これはあまり実用的ではありません。 1つのオプションは、プレースホルダー値(または単に空の<span>)を使用することです。 )そしてjavascriptを入力します。この種のアプローチは「javascriptスプリンクル」と呼ばれることが多く、Railsのコアコードが多数開発されているBasecampで広く支持されているアプローチです。結果は次のようになります:

<% cache(@user) do %>
  <div>
    Hi <%= @user.name %>,
    hope you're having a great
    <span id='greeting-day-name'>Day</span>!
  <div>

  ...
<% end %>

<script>
 // assuming you're using vanilla JS with turbolinks
 document.addEventListener(
   "turbolinks:load", function() {
   weekdays = new Array('Sunday', 'Monday',
     'Tuesday', 'Wednesday', 'Thursday',
     'Friday', 'Saturday');
     today = weekdays[new Date().getDay()];
   document.getElementById("greeting-day-name").textContent=today;
 });
</script>

別のアプローチは、ビューの一部のみをキャッシュすることです。この例では、挨拶はページの上部にあるため、次の内容のみをキャッシュするのは非常に簡単です。

<div>
  Hi <%= @user.name %>,
  hope you're having a great
  <%= Date.today.strftime("%A") %>!
<div>

<% cache(@user) do %>
  ...
<% end %>

明らかに、これは現実の世界で見られるレイアウトではそれほど単純ではないことが多いため、キャッシュをどこにどのように適用するかを考慮する必要があります。

警告の言葉

ビューキャッシングは、パフォーマンスの問題に対する迅速で簡単な万能薬と見なすのは簡単です。確かに、Railsはそれを信じられないほど ビューとパーシャルは、深くネストされている場合でも簡単にキャッシュできます。このシリーズの最初の記事では、システムにキャッシュを追加するときに発生する可能性のある問題について説明しましたが、これは特にビューレベルのキャッシュに当てはまると思います。

その理由は、ビューはその性質上、システムの基礎となるデータとより多くの相互作用を持つ傾向があるためです。 Railsでメモ化または低レベルのキャッシュを適用する場合、キャッシュされた値をいつ、なぜ更新する必要があるかを判断するために、ファイルの外部を調べる必要がないことがよくあります。一方、ビューには複数の異なるモデルが呼び出される可能性があり、慎重な計画がないと、どのモデルがビューのどの部分をいつ再レンダリングするかを確認するのが難しい場合があります。

低レベルのキャッシングと同様に、最良のアドバイスは、いつどこでそれを使用するかについて戦略的に考えることです。許容可能なレベルのパフォーマンスを達成するために、できるだけ少ない場所で、できるだけ少ないキャッシュを使用してください。

デフォルトでのRailsのキャッシング

キャッシュに関するこのシリーズのこれまでのところ、手動​​でキャッシュする方法について説明してきましたが、手動で構成しなくても、ActiveRecordはクエリを高速化する(または完全にスキップする)ために、すでに内部でキャッシュを実行しています。キャッシングに関するこのシリーズの次の記事では、ActiveRecordがキャッシングしているものと、少量の作業でthing.children.size 最新のカウントを取得するためにデータベースにアクセスする必要はまったくありません。


  1. Bluetooth 5 について知っておくべきことすべて

    Wi-Fi と同様に、Bluetooth はモバイルやその他のデバイスに不可欠な要素です。ファイルやデータの転送に使用されるだけでなく、スピーカー、スポーツ イヤホンなどのワイヤレス デバイスを接続することもできます。 Bluetooth は通信の方法を変えただけでなく、多くのデバイスをかさばるものからコンパクトなものに変えました。この新しいバージョンの Bluetooth では、以前のバージョンではできなかった多くのことが可能になります。ただし、新しいバージョンはそれほど例外的ではありません。しかし同時に、それはまだいくつかの新鮮なものをテーブルにもたらします.それでは、何が用意されている

  2. PBM ファイルについて知っておくべきことは?

    コンピューターが親しみやすいものになって以来、テクノロジーとエンターテインメントの分野で顕著な成長を目の当たりにすることができます。娯楽の最も一般的なソースの 1 つは、写真やその他のメディアです。ただし、現在のテクノロジでは、ファイルをサポートできる特定のツールを開くために正しいプログラムを選択する必要はありません。ただし、画像とその他のメディア ファイルは同じファイル タイプではなく、さまざまな拡張子が関連付けられていることを知っておくとよいでしょう。そのような拡張子の 1 つは、しばらくの間ユーザーの間で人気を集めている PBM ファイル拡張子です。今日は、PBM ファイルとその実行方法