Skip Navigation
Show nav
Heroku Dev Center
  • Get Started
  • ドキュメント
  • Changelog
  • Search
  • Get Started
    • Node.js
    • Ruby on Rails
    • Ruby
    • Python
    • Java
    • PHP
    • Go
    • Scala
    • Clojure
  • ドキュメント
  • Changelog
  • More
    Additional Resources
    • Home
    • Elements
    • Products
    • Pricing
    • Careers
    • Help
    • Status
    • Events
    • Podcasts
    • Compliance Center
    Heroku Blog

    Heroku Blog

    Find out what's new with Heroku on our blog.

    Visit Blog
  • Log inorSign up
View categories

Categories

  • Heroku のアーキテクチャ
    • Dyno (アプリコンテナ)
    • スタック (オペレーティングシステムイメージ)
    • ネットワーキングと DNS
    • プラットフォームポリシー
    • プラットフォームの原則
  • コマンドライン
  • デプロイ
    • Git を使用したデプロイ
    • Docker によるデプロイ
    • デプロイ統合
  • 継続的デリバリー
    • 継続的統合
  • 言語サポート
    • Node.js
    • Ruby
      • Bundler の使用
      • Rails のサポート
    • Python
      • Python でのバックグランドジョブ
      • Django の使用
    • Java
      • Maven の使用
      • Java でのデータベース操作
      • Spring Boot の使用
      • Java の高度なトピック
    • PHP
    • Go
      • Go の依存関係管理
    • Scala
    • Clojure
  • データベースとデータ管理
    • Heroku Postgres
      • Postgres の基礎
      • Postgres Getting Started
      • Postgres のパフォーマンス
      • Postgres のデータ転送と保持
      • Postgres の可用性
      • Postgres の特別なトピック
    • Heroku Redis
    • Apache Kafka on Heroku
    • その他のデータストア
  • モニタリングとメトリクス
    • ログ記録
  • アプリのパフォーマンス
  • アドオン
    • すべてのアドオン
  • 共同作業
  • セキュリティ
    • アプリのセキュリティ
    • ID と認証
    • コンプライアンス
  • Heroku Enterprise
    • Private Space
      • インフラストラクチャネットワーキング
    • Enterprise Accounts
    • Enterprise Team
    • Heroku Connect (Salesforce 同期)
      • Heroku Connect の管理
      • Heroku Connect のリファレンス
      • Heroku Connect のトラブルシューティング
    • シングルサインオン (SSO)
  • パターンとベストプラクティス
  • Heroku の拡張
    • Platform API
    • アプリの Webhook
    • Heroku Labs
    • アドオンのビルド
      • アドオン開発のタスク
      • アドオン API
      • アドオンのガイドラインと要件
    • CLI プラグインのビルド
    • 開発ビルドパック
    • Dev Center
  • アカウントと請求
  • トラブルシューティングとサポート
  • Integrating with Salesforce
  • 言語サポート
  • Ruby
  • Rails のサポート
  • Rails を使用した Ruby での HTTP キャッシング

This article was contributed by Vincent Spehner

Vincent Spehner is a technology addict working for Tquila interested in software architecture and development patterns. He is currently writing a book explaining best practices for the integration of Heroku and Salesforce apps.

Rails を使用した Ruby での HTTP キャッシング

日本語 — Switch to English

この記事の英語版に更新があります。ご覧の翻訳には含まれていない変更点があるかもしれません。

最終更新日 2021年10月05日(火)

Table of Contents

  • Rails 3 のデフォルトの HTTP キャッシング
  • 時間ベースのキャッシュヘッダー
  • 条件付きキャッシュヘッダー
  • リソースの遅延ロード
  • パブリックリクエスト
  • プライベートコンテンツ
  • キャッシュ不可能なコンテンツ

Rails 3 では、静的なページやアセットのためのシンプルな HTTP キャッシング設定が最初から提供されています。この設定でもアプリケーションにはメリットがありますが、動的なものを含めたすべてのリクエストに対して適切なキャッシュヘッダー​を指定することで、応答時間、ユーザーエクスペリエンス、アプリケーションの動作に必要なリソースが桁違いに改善されます。

この記事では、Rails 3 アプリケーションで HTTP キャッシュヘッダーを利用することで、最小限の変更で応答時間を改善できるいくつかのユースケースについて説明します。

この記事のリファレンスアプリケーションのソースコードは GitHub で 公開されており、実際の動作は https://http-caching-rails.herokuapp.com​ で確認できます。

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

Rails 3 アプリのデフォルト設定には、最も基本的なシナリオでの HTTP キャッシュヘッダーの使用が含まれています。さらに、静的アセットの配信効率を高めるアセットパイプライン​と、控えめなキャッシングメカニズムとして機能する Rack::Cache​ および Rack::ETag​ ミドルウェアが設定されています。

アセットパイプライン

Rails 3.1 以降では、アセットパイプライン​の概念が導入されています。JS および CSS アセットの連結と圧縮に加えて、Rails では、複数のリクエストをまたいだ同一アセットの再取得を防ぐための HTTP キャッシュヘッダーが追加されました。アセットのリクエストには、アセットをローカルでどのように保存するかを定義する複数のヘッダーが含まれます。

アセットパイプラインの応答

  • Age​ ヘッダーは、リソースがキャッシュから取得されてからの推定経過時間を示しています。
  • Cache-Control​ は、このアセットが public​ (中間のプロキシに保存可能) であり、max-age​ の値が 31,536,000 秒 (365 日) であることを示しています。
  • Etag​ は、応答本体のダイジェストに基づいて Rack ミドルウェアによって計算されます。
  • Last-Modified​ は、ファイル内の情報に基づいた最新の変更日を示しています。

ほとんどのアプリケーションでは、これらのデフォルト値で十分であり、アセットパイプラインの変更は必要ありません。

Rack::Cache

Rack::Cache は使用しないでください。代わりに CDN​ を使用してください。

Rails 3 では、Rack::Cache がネイティブのプロキシキャッシュとして導入されました。本番モードでは、public​ キャッシュヘッダーがあるすべてのページが、中間プロキシとして機能する Rack::Cache に保存されます。その結果、キャッシュされたリソースに対するこれらのリクエストでは Rails スタックがバイパスされます。

Rack::Cache では、デフォルトでインメモリストレージが使用されます。Heroku などの高度分散環境では、共有キャッシュリソースを使用することをお勧めします。パフォーマンスの高い HTTP ヘッダーベースのリソースキャッシングを Heroku で実現するには、Rack::Cache と Memcached アドオン​を使用してください。

Rack::ETag

Cache-Control: private​ ヘッダーの副作用は、(Rack::Cache を含めた) リバースプロキシキャッシュにはこれらのリソースが保存されないことです。

Rack::ETag は、すべての応答に自動的に ETag​ ヘッダーと Cache-Control: private​ を割り当てる​ことによって、条件付きリクエスト​のサポートを提供します。

動的ページヘッダー

ビューがレンダリングされた後に完全な形式の応答文字列をハッシュ化することにより、アプリケーションの詳細を知らなくてもこれを行うことができます。

このアプローチはアプリケーションに対して透過的ですが、それでもアプリケーションでは、リクエストを完全に処理して応答本体をハッシュ化する必要があります。完全な応答の代わりに空の応答が 304 Not Modified​ 応答ステータスと共に送信されるため、ネットワーク経由でエンドクライアントに完全な応答を送り返すコストが節約されるだけです。

パフォーマンス最大化のためにキャッシュヘッダーを設定するのがアプリケーション開発者の責任であることに変わりはありません。

時間ベースのキャッシュヘッダー

Rails では、時間ベースのリソースのキャッシングを Expires​ HTTP ヘッダー​を介して指定するためのコントローラーメソッドとして、expires_in​ と expires_now​ の 2 つを提供しています。

expires_in

Cache-Control​ ヘッダーの max-age​ の値は、(サンプルアプリ)​の show​ アクションで使用される) expires_in​ コントローラーメソッド​ を使用して設定されます。

def show
  @company = Company.find(params[:id])
  expires_in 3.minutes, :public => true
  # ...
end

会社のリソースに対してリクエストが行われるときは、Cache-Control​ ヘッダーが適切に設定されます。

expires_in のログ

max-age​ の値で指定された時間が経過するまで、クライアントはリソースをリクエストしません。これは、キャッシングに対する粒度の粗いアプローチとして機能し、変更頻度が低く、変更されてもすぐに伝播する必要のないコンテンツに適しています。

Rack::Cache のリクエストと組み合わせて使用する場合、これらのリソースに対するリクエストは、指定された期間中、コントローラーに 1 回しかヒットしません。

Started GET "/companies/2" for 127.0.0.1 at 2012-09-26 14:07:28 +0100
Processing by CompaniesController#show as HTML
  Parameters: {"id"=>"2"}
  Rendered companies/show.html.erb within layouts/application (9.0ms)
Completed 200 OK in 141ms (Views: 63.8ms | ActiveRecord: 14.4ms)

Started GET "/companies/2" for 127.0.0.1 at 2012-09-26 14:11:10 +0100
Processing by CompaniesController#show as HTML
  Parameters: ​{"id"=>"2"}
Completed 304 Not Modified in 2ms (ActiveRecord: 0.3ms)

最初のリクエストはレンダリングを表示するために完全に実行される一方、2 番目のリクエストはただちに 304 Not Modified​ を返すことに注意してください。

expires_now

expires_now​ コントローラーメソッド​を使用して、リソースを強制的に期限切れにする​ことができます。このメソッドは、Cache-Control​ ヘッダーを no-cache​ に設定して、ブラウザまたは中間キャッシュによるキャッシングが行われないようにします。

def show
  @person = Person.find(params[:id])

  # Set Cache-Control header no-cache for this one person
  # (just as an example)
  expires_now if params[:id] == '1'
end

Cache-Control​ ヘッダーがゼロで埋められ、リソースは強制的に期限切れになります。

expires_now_logs

expires_now​ は、コントローラーアクションを呼び出すリクエストにしか実行されません。以前に expires_in​ でヘッダーが設定されたリソースは、有効期限が過ぎるまで、更新されたリソースをすぐにリクエストしません。開発/デバッグ時はこのことに注意してください。

条件付きキャッシュヘッダー

条件付き GET​ リクエスト​では、ブラウザがリクエストを開始する必要があります。一方でサーバーは、共有メタデータ (ETag​ ハッシュまたは Last-Modified​ タイムスタンプ) に基づいて、キャッシュされた応答を返すか、処理を完全にバイパスすることができます。

Rails では、stale?​ および fresh_when​ メソッドを使用して適切な条件付き動作を指定します。

stale?

stale?​ コントローラーメソッド​は、適切な ETag​ および Last-Modified-Since​ ヘッダーを設定します。また、現在のリクエストが古くなっている (完全に処理する必要がある) か、それともまだ新しい (Web クライアントはリクエストのキャッシュされたコンテンツを使用できる) かを判定します。

パブリックリクエストの場合は、追加されたリバースプロキシキャッシングのために :public => true​ を指定します。

def show
  @company = Company.find(params[:id])
  # ...
  if stale?(etag: @company, last_modified: @company.updated_at)
    respond_to do |format|
      format.html # show.html.erb
      format.json { render json: @company }
    end
  end
end

respond_to​ を stale?​ ブロック内にネストすると、そのビューのレンダリング​が保証されます。これは多くの場合、リクエストの中で最もコストが高い部分であり、必要時にしか実行されません。

ActiveRecord ドメインオブジェクトと、最終変更時刻としてその updated_at​ タイムスタンプを使用して stale?​ を呼び出すパターンが一般的です。Rails では、オブジェクト自体を唯一の引数として許可することでこれをサポートしています。この例は stale?(@company)​ のように実装できます。

if stale?(@company)
  respond_to do |format|
    # ...
  end
end

この設定では、Companies#show​ の最初のリクエストではリクエストスタック全体が呼び出されます (パフォーマンスは向上しません)。

header-stale

しかし、それ以降のリクエストではビューのレンダリングをスキップして 304 Not modified​ を返し、リクエストのうち最もコストが高い部分を回避します。

header-not-stale

応答の裏付けとなるコアオブジェクトが古くなっていないことが確認できればリクエスト全体の処理をバイパスできるため、304​ 応答ステータスにより、ブラウザのロードの観点から高速化されるだけでなくサーバー側での効率も高まります。

fresh_when

stale?​ メソッドはブール値を返すので、リクエストがまだ新しいかどうかに応じて異なるパスを実行できます。一方、fresh_when​ は ETag​ および Last-Modified-Since​ 応答ヘッダーを設定するだけであり、リクエストがまだ新しい場合は 304 Not Modified​ 応答ステータスも設定します。カスタムの実行処理が不要な (デフォルト実装の) コントローラーアクション​には fresh_when​ を使用することをお勧めします。

def index
  @people = Person.scoped
  fresh_when last_modified: @people.maximum(:updated_at), public: true
end

リソースの遅延ロード

ここで説明する HTTP ヘッダーのキャッシングアプローチを使用すれば、リクエスト処理のうちビューのレンダリング部分をバイパスできます。したがって、できるだけ多くの処理をビューに先送りすると有利です。通常の実行では、Person.all​ のコントローラーアクション呼び出しによって、すべての Person​ レコード (モデルの関連付けによっては、これに加えてすべての子オブジェクト) がデータベースから取得およびロードされます。

Started GET "/people" for 127.0.0.1 at 2012-09-26 15:08:15 +0100
Processing by PeopleController#index as HTML
  Person Load (0.2ms)  SELECT "people".* FROM "people"
  Company Load (0.4ms)  SELECT "companies".* FROM "companies" WHERE "companies"."id" = 1 LIMIT 1
  Company Load (0.4ms)  SELECT "companies".* FROM "companies" WHERE "companies"."id" = 2 LIMIT 1
  Rendered people/index.html.erb within layouts/application (2023.8ms)
Completed 200 OK in 2030ms (Views: 2023.7ms | ActiveRecord: 5.2ms)

一方、コントローラーで ActiveRelation スコープを使用する場合、データベースからのオブジェクトのロードは、ビューでオブジェクトが必要になる時点まで先送りされます。

def index
  @people = Person.scoped
  fresh_when last_modified: @people.maximum(:updated_at)
end

HTTP キャッシングによってビューの処理が回避されれば、必要なデータベース呼び出しが減り、多大な追加効果が得られます。

Started GET "/people" for 127.0.0.1 at 2012-09-26 15:09:43 +0100
Processing by PeopleController#index as HTML
 (0.4ms)  SELECT MAX("people"."updated_at") AS max_id FROM "people"
Completed 304 Not Modified in 1ms (ActiveRecord: 0.4ms)

コントローラーアクションで、名前付きスコープ、または ActiveRecord クエリメソッド​のいずれかをまだ使用していない場合、匿名スコープメソッド scoped​ を使用して、all​ ファインダーメソッドと同等のスコープを作成します。

パブリックリクエスト

パブリック応答には機密データが含まれず、中間プロキシキャッシュによって保存できます。キャッシングメソッドで public: true​ を使用してパブリックリソースを識別します。

def show
  @company = Company.find(params[:id])
  expires_in(3.minutes, public: true)
  if stale?(@company, public: true)
    # …
  end
end

プライベートコンテンツ

デフォルトでは、Cache-Control​ はすべてのリクエストに対してプライベートに設定されます。ただし、一部のキャッシュ設定によってデフォルト動作が上書きされる可能性があるため、プライベートリソースを明示的に指定することをお勧めします。

expires_in(1000.seconds, public: false)

キャッシュ不可能なコンテンツ

コンテンツのキャッシュを回避するためのグローバルなアプローチは before_filter​ の使用です。これは、コントローラーの継承ツリーで定義することも、明示的なプライベート設定を使用してコントローラーごとに定義することもできます。

before_filter :set_as_private

def set_as_private
  expires_now
end

即時の期限切れ

Rails では、静的アセットのための基本レベルの HTTP キャッシングがデフォルトで提供されます。ただし、エクスペリエンスを真に最適化するには、Rails の豊富なリクエストキャッシング機能のいずれかを使用して、アプリケーション全体で HTTP キャッシングヘッダーを明示的に定義することをお勧めします。

関連カテゴリー

  • Rails のサポート
Unicorn を使用した Rails アプリケーションのデプロイ Rails でのキャッシング戦略

Information & Support

  • Getting Started
  • Documentation
  • Changelog
  • Compliance Center
  • Training & Education
  • Blog
  • Podcasts
  • Support Channels
  • Status

Language Reference

  • Node.js
  • Ruby
  • Java
  • PHP
  • Python
  • Go
  • Scala
  • Clojure

Other Resources

  • Careers
  • Elements
  • Products
  • Pricing

Subscribe to our monthly newsletter

Your email address:

  • RSS
    • Dev Center Articles
    • Dev Center Changelog
    • Heroku Blog
    • Heroku News Blog
    • Heroku Engineering Blog
  • Heroku Podcasts
  • Twitter
    • Dev Center Articles
    • Dev Center Changelog
    • Heroku
    • Heroku Status
  • Facebook
  • Instagram
  • Github
  • LinkedIn
  • YouTube
Heroku is acompany

 © Salesforce.com

  • heroku.com
  • Terms of Service
  • Privacy
  • Cookies
  • Cookie Preferences