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

Railsのフラグメントキャッシングでビュー描画を高速化する方法

Railsアプリケーションがリクエストを受け取ると、通常はコントローラーがモデルに対象データの取得を依頼します。モデルはデータベースからデータを取り出してコントローラーへ返し、最後にコントローラーがそのデータを人間が読みやすい形式で表現するビューをレンダリングします。

しかし場合によっては、ビューのレンダリングが大きなボトルネックになることがあります。特に、ストアで販売中の商品一覧のように大量のデータを表示するケースでは顕著です。こうした場面では、ビューの一部をキャッシュすることでレスポンスを高速化できます。データの更新頻度が低いほど、その効果は大きくなります。

👋 この記事が役に立った方は、Ruby(Rails)のパフォーマンスに関する記事も多数公開しています。ぜひ「Rubyパフォーマンス監視チェックリスト」もあわせてご覧ください。

ローカル環境でキャッシュをテストする

開発環境では、常にアプリケーションからの最新のレスポンスを確認できるよう、デフォルトでキャッシュが無効になっています。ローカルでキャッシュの挙動を試したい場合は、開発環境の設定でキャッシュを有効にする必要があります。

Rails 5では、コマンドラインから一時的にキャッシュをオンにできます。この場合、メモリストアが使用され、キャッシュされたフラグメントはWebサーバーのRubyプロセスのメモリ上に保持されます。

$ rails dev:cache
Development mode is now being cached.

同じコマンドをもう一度実行すれば、キャッシュを元のオフ状態に戻せます。

フラグメントキャッシングとは

ここでは、ストア内のすべての商品を1ページに表示する画面を例に考えてみましょう。商品一覧を表示するindexビューは次のとおりです。

# app/views/products/index.html.erb
<table>
  <thead>
    <tr>
      <th>タイトル</th>
      <th>説明</th>
      <th>画像URL</th>
      <th>価格</th>
      <th colspan="3"></th>
    </tr>
  </thead>
 
  <tbody>
    <% @products.each do |product| %>
      <%= render product %>
    <% end %>
  </tbody>
</table>

各商品ごとに _product.html.erb パーシャルがレンダリングされ、商品の詳細情報を含むテーブル行が出力されます。

# app/views/products/_product.html.erb
<tr>
  <td><%= product.title %></td>
  <td><%= product.description %></td>
  <td><%= product.image_url %></td>
  <td><%= product.price %></td>
  <td><%= link_to '詳細', product %></td>
  <td><%= link_to '編集', edit_product_path(product) %></td>
  <td><%= link_to '削除', product, method: :delete, data: { confirm: '本当に削除しますか?' } %></td>
</tr>

開発モードでこのページにローカルからアクセスすると、データベースに25件の商品が存在する状態で約300ミリ秒かかります。その大部分はパーシャルのレンダリングに費やされています。

本番環境では、アセットのバンドル、ログ出力の削減、より高性能なWebサーバーなどの要因により、レスポンスはおそらくもう少し速くなります。開発環境の数値は厳密なものではありませんが、リクエスト処理の中でどの部分が最も遅いのかを把握でき、ボトルネックの特定と改善につながります。

Started GET "/products" for ::1 at 2018-03-13 12:16:08 +0100
Processing by ProductsController#index as HTML
  Rendering products/index.html.erb within layouts/application
  Product Load (0.4ms)  SELECT "products".* FROM "products"
  Rendered products/_product.html.erb (1.4ms)
  Rendered products/_product.html.erb (0.4ms)
  ...(中略:全25件分のパーシャル描画)
  Rendered products/index.html.erb within layouts/application (257.5ms)
Completed 200 OK in 295ms(Views: 290.4ms | ActiveRecord: 0.4ms)

これらのパーシャルを毎回レンダリングする手間を省くには、Railsに組み込まれたフラグメントキャッシングを活用します。これは、レンダリング済みのビューの一部をフラグメントとして保存しておき、以降のリクエストでは再レンダリングを行わず、保存済みのフラグメントをそのまま利用する仕組みです。

フラグメントをキャッシュするには、cacheヘルパーで対象部分をブロックとして囲むだけです。

<table>
  # ...
  <tbody>
    <% @products.each do |product| %>
      <% cache(product) do %>
        <%= render product %>
      <% end %>
    <% end %>
  </tbody>
</table>

キャッシュによる高速化を確認するため、同じページに2回アクセスしてみましょう。2回目のリクエストでは、ビュー内の各商品がすでにレンダリング済みでキャッシュに保存されているため、処理が大きく速くなるはずです。

Started GET "/products" for ::1 at 2018-03-13 12:17:29 +0100
Processing by ProductsController#index as HTML
  Rendering products/index.html.erb within layouts/application
  Product Load (0.4ms)  SELECT "products".* FROM "products"
  Rendered products/index.html.erb within layouts/application (21.2ms)
Completed 200 OK in 55ms (Views: 50.8ms | ActiveRecord: 0.4ms)

成功です!2回目のリクエストは1回目の5倍以上高速になりました。ログにパーシャルのレンダリング記録が一切表示されていない点にも注目してください。これは、パーシャルがキャッシュから直接読み込まれたことを意味します。

キャッシュの失効(期限切れ)の仕組み

先ほどの例では、cacheヘルパーを呼び出す際にproductオブジェクトをキャッシュ依存オブジェクトとして渡しました。これにより、キャッシュされたフラグメントの内容がproductオブジェクトに依存していることがヘルパーに伝わります。

内部的には、productオブジェクトが持つ #cache_key メソッドを使って、キャッシュフラグメント用のキーが生成されます。生成されるキーはたとえば次のような形式です。

views/products/42-20180302103130041320/75dda06d36880e8b0ae6cac0a44fb56d

このキャッシュキーは、以下の複数の要素から構成されています。

  • views/products:キャッシュのクラス(種別)
  • 42:商品のID
  • 20180302103130041320:商品のupdated_at(最終更新日時)
  • 75dda06d36880e8b0ae6cac0a44fb56d:テンプレートツリーのダイジェスト

商品が更新されたり、テンプレートの内容が変わったりすると、このキーも変化します。キーが変わるとキャッシュミスが発生し、フラグメントが再レンダリングされて新しい内容が保存されます。この仕組みのおかげで、構成要素が変化してもフラグメントは常に新鮮な状態に保たれるのです。

フラグメントキャッシングのその先へ

今回の例で紹介したキャッシュフラグメントでも一定の高速化が得られますが、キャッシングの世界はこれだけにとどまりません。「ロシアンドールキャッシング」のような戦略や、データベースのクエリ結果をキャッシュするといった低レベルな手法を組み合わせれば、さらなる大幅な高速化が可能です。

これらのトピックについては、AppSignal Academyの今後の記事で詳しく取り上げる予定です。特に学びたいテーマがあれば、@AppSignalまでお気軽にご連絡ください。本記事へのフィードバックや、他に知りたい題材があればぜひお聞かせください。

  1. HTML段落(pタグ)とは?基本構文と使い方を実例付きで解説

    HTMLの段落は、<p>タグを使用して作成される段落要素です。<p>タグはブロックレベル要素に分類され、テキストを論理的なまとまりとして扱うことができます。文章を読みやすく構造化するうえで、最も基本的かつ重要な要素の一つと言えるでしょう。 基本構文 HTML段落の基本的な構文は以下のとおりです。 <p>コンテンツ</p> <p>タグで囲まれたテキストが一つの段落として表示されます。開始タグと終了タグは必ずペアで記述してください。 使用例 それでは、HTML段落の具体的な使用例を見ていきましょう。以下のコードでは、見出し(h1)と

  2. HTMLレイアウト入門:セマンティック要素と主要なレイアウト手法を徹底解説

    HTMLレイアウトとは HTMLレイアウトとは、Webページ上に配置される各コンポーネント(ヘッダー、ナビゲーション、本文、フッターなど)の配置構造を定義する仕組みのことです。HTMLには、ページ内のさまざまなセクションを意味的に示すためのセマンティック要素が数多く用意されており、これらを活用することで、検索エンジンやスクリーンリーダーにも伝わりやすい、アクセシブルなページ構造を作れます。 HTMLレイアウトで使用される主なセマンティック要素 以下は、HTMLレイアウトでよく使われるセマンティック要素の一覧です。 タグ説明 <header>セクションまたはドキュメント全体