Webアプリケーションでよく使われる様々なcacheをまとめます
24955 ワード
add by zhj:まだ見ていないので、暇があればもっと詳しく見てください.
原文:https://ruby-china.org/topics/19389
Cacheは応用性能を高める重要な一環であり,使用した動的コンテンツに対する様々なcacheをまとめた文章を書く.記事では,Nginx,Rails,Mysql,Redisを例に,他のウェブサーバ,言語,データベース,キャッシュサービスに置き換えると類似している.以下は3階層の概略図で、後続の参照を容易にします.
1.クライアントキャッシュ
ブラウザでWebサイトのトップページにアクセスしたり、同じ記事を表示したり、appで同じapiにアクセスしたりするなど、同じリソースにアクセスすることがよくあります.このリソースが以前にアクセスしたものと何の変更もなければ、http仕様の304 Not Modified応答ヘッダ(http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5)を利用して、クライアントのキャッシュを直接使用することができます.サーバ側でコンテンツを再生成する必要はありません.Railsにはfreshが内蔵されていますwhenという方法では、1行のコードで完了できます.
次回ユーザーが再アクセスする場合、request headerのIf-modified-SinceとIf-Nene-Martchを比較し、一致すればresponse bodyを生成せずに304に戻ります.
しかし、これは問題に直面します.もし私たちのウェブサイトのナビゲーションにユーザー情報があるとしたら、1人のユーザーがログインしていないテーマにアクセスし、ログインしてからアクセスすると、ページに表示されているのかログインしていない状態なのかがわかります.あるいはappで1つの文章にアクセスして、コレクションをして、今度この文章に入って、やはり未コレクションの状態を表示します.この問題を解決する方法は簡単で、ユーザー関連の変数もetagの計算に追加します.
もう一つのピットは、nginxがgzipを開いてrailsが実行した結果を圧縮すると、railsが出力したetag headerが乾くと、nginxの開発者はrfc規範に基づいてproxy_pass方式処理はこうしなければならない(内容が変わったので)が、個人的にはそんな必要はないと思い、乱暴な方法でsrc/http/modules/ngx_http_gzip_filter_module.cこのファイルの中のこの行のコードは注釈して、それからnginxを再コンパイルします:
あるいは、nginxソースコードを変更せずにgzip offを削除し、圧縮をRackミドルウェアで処理することもできます.
コントロールでfreshを指定する以外はその他、railsフレームワークのデフォルトではRack::ETag middlewareが使用され、etagのないresponseに自動的にetagが加算されますが、fresh_whenに比べて、自動etagはクライアント時間だけを節約でき、サーバ側は同じようにすべてのコードを実行し、curlで比較します.Rack::ETag自動加入etag:
fresh_でwhen:
2.Nginxキャッシュ
一部のリソースは、ユーザーの状態に関係なく呼び出される可能性があり、ニュースappのリストapi、ショッピングサイトのajaxリクエスト分類メニューなど、変更は少なく、Nginxでキャッシュすることが考えられます.主に2つの実現方法がある:A.動的要求静的ファイル化rails要求が完了した後、結果を静的ファイルに保存し、後続の要求はnginxから直接静的ファイル内容を提供し、after_フィルタで実現します.
また、分類の更新時にこのファイルを削除し、キャッシュがリフレッシュされないという問題を回避する必要があります.
Rails 4の前に、このような生成静的ファイルキャッシュを処理するには、内蔵caches_を使用することができます.Page,rails 4はその後独立gem actionpack-page_になりましたcaching、手動コードと比較して、
1台のサーバしかない場合、この方法は簡単で実用的ですが、複数のサーバがある場合、更新分類が自分自身のサーバキャッシュのみをリフレッシュできるという問題が発生します.nfsで静的リソースディレクトリを共有して解決するか、2つ目を使用します.
B.集中キャッシュサービスに静態化するには、まずNginxにキャッシュに直接アクセスする能力を持たせなければならない.
Nginxはまずリクエストしたuriをkeyとしてredisに取得し、取得できなければ(404)unicornに転送して処理し、generate_を書き換えるstatic_fileとdelete_static_fileメソッド:
これにより、集中管理に加えて、キャッシュの失効時間を設定することができ、時効性の要求がないデータを更新する場合、リフレッシュメカニズムを処理することなく、簡単に時間を固定してリフレッシュすることができる.
3.全ページキャッシュ
Nginxキャッシュは,パラメータ付きリソースやユーザ状態のあるリクエストを処理する際に非常に扱いにくく,この場合はページ全体のキャッシュを用いることができる.たとえば、ページング要求リストでは、pageパラメータをcache_に追加できます.path:
たとえばrss出力を8時間キャッシュするだけです.
例えば、非ログインユーザーの場合、トップページをキャッシュできます.
4.クリップキャッシュ
前の2つのキャッシュで使用できるシーンが限られている場合、クリップキャッシュは最も広く適用されます.
シーン1:各ページに異なる広告を表示するための広告コードが必要です.クリップキャッシュを使用していない場合、各ページは広告のコードを検索し、htmlコードを生成するのに時間がかかります.
クリップキャッシュを追加すると、このクエリを少なくすることができます.
シーン2:文章を読むと、文章の内容は長い間変わらない可能性があります.よく変化するのは文章のコメントかもしれません.文章の本体部分にクリップキャッシュを加えることができます.
markdown構文をhtmlに変換する時間を節約しました.ここでは、文章の最後の更新時間をcache keyの一部として使用します.文章の内容が変更されると、キャッシュが自動的に失効し、デフォルトactiverecordのcache_keyメソッドもupdated_at、articleにコメント数のあるcounter cacheなど、より多くのパラメータを追加することもできます.コメント数を更新するときは文章時間を更新しません.このcounterもkeyの一部に追加することができます.
シーン3:复雑なページ构造のデータ构造の比较的复雑なページを生成して、生成する时大量のクエリーとhtmlのレンダリングを避けることができなくて、断片のキャッシュで、この部分の时间を大いに节约することができて、私达のウェブサイトの旅行记のページのhttp://chanyouji.com/trips/109123(小さく広告を打って、流量を持って)にとって:天気のデータを得る必要があって、写真のデータ、テキストデータなど、meta、keywordなどのseoデータも生成されますが、これらのコンテンツは他のダイナミックコンテンツと交差し、クリップキャッシュは複数に分けることができます.
小贴士、tripオブジェクトにeagerを追加しました.load_allメソッド、キャッシュがヒットしていない場合、クエリー時にn+1の問題を回避します.
テクニック1:条件付きクリップキャッシュとcaches_Actionとは異なり、railsが持参したクリップキャッシュは条件をサポートしていません.例えば、ユーザーにログインしていないでクリップキャッシュを使いたいと思っていますが、ログインユーザーが使用しないので、書くのが面倒です.helperを書き換えることができます.
テクニック2:関連オブジェクトの自動更新常使用オブジェクトupdate_atタイムスタンプはcache keyとして使用され、関連オブジェクトにtouchオプションを追加して、関連オブジェクトタイムスタンプを自動的に更新することができます.たとえば、記事コメントを更新または削除するときに、自動的に更新することができます.
5.データ照会キャッシュ
通常、WebアプリケーションのパフォーマンスのボトルネックはDB IOに現れ、データクエリーのキャッシュをしっかりと行い、データベースのクエリー回数を減らし、全体の応答時間を大幅に向上させることができます.データ照会キャッシュは2種類あります.A.同じリクエストサイクル内のキャッシュは、文章リストを表示する例を挙げて、文章タイトルと文章カテゴリを出力します.対応コードは以下の通りです.
同様のsqlクエリが10件発生します.
railsにはquery cache(https://github.com/rails/rails/blob/master/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb)が内蔵されており、同じリクエストサイクルでupdate/delete/insertの操作がなければ、同じsqlクエリーがキャッシュされ、文章カテゴリが同じであれば、本当にデータベースをクエリーするのは1回しかありません.
記事のカテゴリが異なる場合は、N+1クエリの問題(一般的なパフォーマンスボトルネック)が発生します.railsが推奨する解決策は、Eager Loading Associations(http://guides.rubyonrails.org/active_record_querying.html#eager-loading-associations)です.
クエリ文は
B.要求周期にまたがるキャッシュと要求周期キャッシュがもたらす性能の最適化は限られており、多くの場合、要求周期にまたがるキャッシュを使用して、User modelなどの一般的なデータをキャッシュする必要があります.active recordにとって、統一的なクエリーインタフェースを利用してfetch cacheを利用し、callbackを利用してexpire cacheを利用すると、実現しやすいです.そして、既存のgemが使えます.
例えばidentity_cache ( https://github.com/Shopify/identity_cache )
このgemの利点は、コード実装が簡単で、cache設定が柔軟で、拡張も容易であることであり、欠点は、異なるクエリーメソッド名(fetch)、および追加の関係定義が必要であることである.
データキャッシュのないアプリケーションにシームレスにキャッシュ機能を追加したい場合は、@hooopoのsecond_をお勧めします.level_cache ( https://github.com/hooopo/second_level_cache ) .
実装原理はactive record下位arel sql ast処理(https://github.com/hooopo/second_level_cache/blob/master/lib/second_level_cache/arel/wheres.rb)を拡張することであり、拡張が困難であり、少量のフィールドのみを取得するクエリーではキャッシュできないという利点がある.
6.データベース・キャッシュ
編集中
この6種類のキャッシュは,クライアントからサーバ側まで異なる位置に分布し,節約できる時間もちょうど多くから少ない順に並べられている.
原文:https://ruby-china.org/topics/19389
Cacheは応用性能を高める重要な一環であり,使用した動的コンテンツに対する様々なcacheをまとめた文章を書く.記事では,Nginx,Rails,Mysql,Redisを例に,他のウェブサーバ,言語,データベース,キャッシュサービスに置き換えると類似している.以下は3階層の概略図で、後続の参照を容易にします.
+-------+
1 | Nginx |
+-+-+-+-+
| | |
+---------------+ | +---------------+
| | |
+---+---+ +---+---+ +---+---+
2 |Unicorn| |Unicorn| |Unicorn|
+---+---+ +---+---+ +---+---+
| | |
| | |
| +---+---+ |
3 +-------------+ D B +-------------+
+-------+
1.クライアントキャッシュ
ブラウザでWebサイトのトップページにアクセスしたり、同じ記事を表示したり、appで同じapiにアクセスしたりするなど、同じリソースにアクセスすることがよくあります.このリソースが以前にアクセスしたものと何の変更もなければ、http仕様の304 Not Modified応答ヘッダ(http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5)を利用して、クライアントのキャッシュを直接使用することができます.サーバ側でコンテンツを再生成する必要はありません.Railsにはfreshが内蔵されていますwhenという方法では、1行のコードで完了できます.
class ArticlesController
def show
@article = Article.find(params[:id])
fresh_when :last_modified => @article.updated_at.utc, :etag => @article
end
end
次回ユーザーが再アクセスする場合、request headerのIf-modified-SinceとIf-Nene-Martchを比較し、一致すればresponse bodyを生成せずに304に戻ります.
しかし、これは問題に直面します.もし私たちのウェブサイトのナビゲーションにユーザー情報があるとしたら、1人のユーザーがログインしていないテーマにアクセスし、ログインしてからアクセスすると、ページに表示されているのかログインしていない状態なのかがわかります.あるいはappで1つの文章にアクセスして、コレクションをして、今度この文章に入って、やはり未コレクションの状態を表示します.この問題を解決する方法は簡単で、ユーザー関連の変数もetagの計算に追加します.
fresh_when :etag => [@article.cache_key, current_user.id]
fresh_when :etag => [@article.cache_key, current_user_favorited]
もう一つのピットは、nginxがgzipを開いてrailsが実行した結果を圧縮すると、railsが出力したetag headerが乾くと、nginxの開発者はrfc規範に基づいてproxy_pass方式処理はこうしなければならない(内容が変わったので)が、個人的にはそんな必要はないと思い、乱暴な方法でsrc/http/modules/ngx_http_gzip_filter_module.cこのファイルの中のこの行のコードは注釈して、それからnginxを再コンパイルします:
//ngx_http_clear_etag(r);
あるいは、nginxソースコードを変更せずにgzip offを削除し、圧縮をRackミドルウェアで処理することもできます.
config.middleware.use Rack::Deflater
コントロールでfreshを指定する以外はその他、railsフレームワークのデフォルトではRack::ETag middlewareが使用され、etagのないresponseに自動的にetagが加算されますが、fresh_whenに比べて、自動etagはクライアント時間だけを節約でき、サーバ側は同じようにすべてのコードを実行し、curlで比較します.Rack::ETag自動加入etag:
curl -v http://localhost:3000/articles/1
< Etag: "bf328447bcb2b8706193a50962035619"
< X-Runtime: 0.286958
curl -v http://localhost:3000/articles/1 --header 'If-None-Match: "bf328447bcb2b8706193a50962035619"'
< X-Runtime: 0.293798
fresh_でwhen:
curl -v http://localhost:3000/articles/1 --header 'If-None-Match: "bf328447bcb2b8706193a50962035619"'
< X-Runtime: 0.033884
2.Nginxキャッシュ
一部のリソースは、ユーザーの状態に関係なく呼び出される可能性があり、ニュースappのリストapi、ショッピングサイトのajaxリクエスト分類メニューなど、変更は少なく、Nginxでキャッシュすることが考えられます.主に2つの実現方法がある:A.動的要求静的ファイル化rails要求が完了した後、結果を静的ファイルに保存し、後続の要求はnginxから直接静的ファイル内容を提供し、after_フィルタで実現します.
class CategoriesController < ActionController::Base
after_filter :generate_static_file, :only => [:index]
def index
@categories = Category.all
end
def generate_static_file
File.open(Rails.root.join('public', 'categories'), 'w') do |f|
f.write response.body
end
end
end
また、分類の更新時にこのファイルを削除し、キャッシュがリフレッシュされないという問題を回避する必要があります.
class Category < ActiveRecord::Base
after_save :delete_static_file
after_destroy :delete_static_file
def delete_static_file
File.delete Rails.root.join('public', 'categories')
end
end
Rails 4の前に、このような生成静的ファイルキャッシュを処理するには、内蔵caches_を使用することができます.Page,rails 4はその後独立gem actionpack-page_になりましたcaching、手動コードと比較して、
class CategoriesController < ActionController::Base
caches_page :index
def update
#...
expire_page action: 'index'
end
end
1台のサーバしかない場合、この方法は簡単で実用的ですが、複数のサーバがある場合、更新分類が自分自身のサーバキャッシュのみをリフレッシュできるという問題が発生します.nfsで静的リソースディレクトリを共有して解決するか、2つ目を使用します.
B.集中キャッシュサービスに静態化するには、まずNginxにキャッシュに直接アクセスする能力を持たせなければならない.
upstream redis {
server redis_server_ip:6379;
}
upstream ruby_backend {
server unicorn_server_ip1 fail_timeout=0;
server unicorn_server_ip2 fail_timeout=0;
}
location /categories {
set $redis_key $uri;
default_type text/html;
redis_pass redis;
error_page 404 = @httpapp;
}
location @httpapp {
proxy_pass http://ruby_backend;
}
Nginxはまずリクエストしたuriをkeyとしてredisに取得し、取得できなければ(404)unicornに転送して処理し、generate_を書き換えるstatic_fileとdelete_static_fileメソッド:
redis_cache.set('categories', response.body)
redis_cache.del('categories')
これにより、集中管理に加えて、キャッシュの失効時間を設定することができ、時効性の要求がないデータを更新する場合、リフレッシュメカニズムを処理することなく、簡単に時間を固定してリフレッシュすることができる.
redis_cache.setex('categories', 3.hours.to_i, response.body)
3.全ページキャッシュ
Nginxキャッシュは,パラメータ付きリソースやユーザ状態のあるリクエストを処理する際に非常に扱いにくく,この場合はページ全体のキャッシュを用いることができる.たとえば、ページング要求リストでは、pageパラメータをcache_に追加できます.path:
class CategoriesController
caches_action :index, :expires_in => 1.day, :cache_path => proc {"categories/index/#{params[:page].to_i}"}
end
たとえばrss出力を8時間キャッシュするだけです.
class ArticlesController
caches_action :index, :expires_in => 8.hours, :if => proc {request.format.rss?}
end
例えば、非ログインユーザーの場合、トップページをキャッシュできます.
class HomeController
caches_action :index, :expires_in => 3.hours, :if => proc {!user_signed_in?}
end
4.クリップキャッシュ
前の2つのキャッシュで使用できるシーンが限られている場合、クリップキャッシュは最も広く適用されます.
シーン1:各ページに異なる広告を表示するための広告コードが必要です.クリップキャッシュを使用していない場合、各ページは広告のコードを検索し、htmlコードを生成するのに時間がかかります.
- if advert = Advert.where(:name => request.controller_name + request.action_name, :enable => true).first
div.ad
= advert.content
クリップキャッシュを追加すると、このクエリを少なくすることができます.
- cache "adverts/#{request.controller_name}/#{request.action_name}", :expires_in => 1.day do
- if advert = Advert.where(:name => request.controller_name + request.action_name, :enable => true).first
div.ad
= advert.content
シーン2:文章を読むと、文章の内容は長い間変わらない可能性があります.よく変化するのは文章のコメントかもしれません.文章の本体部分にクリップキャッシュを加えることができます.
- cache "articles/#{@article.id}/#{@article.updated_at.to_i}" do
div.article
= @article.content.markdown2html
markdown構文をhtmlに変換する時間を節約しました.ここでは、文章の最後の更新時間をcache keyの一部として使用します.文章の内容が変更されると、キャッシュが自動的に失効し、デフォルトactiverecordのcache_keyメソッドもupdated_at、articleにコメント数のあるcounter cacheなど、より多くのパラメータを追加することもできます.コメント数を更新するときは文章時間を更新しません.このcounterもkeyの一部に追加することができます.
シーン3:复雑なページ构造のデータ构造の比较的复雑なページを生成して、生成する时大量のクエリーとhtmlのレンダリングを避けることができなくて、断片のキャッシュで、この部分の时间を大いに节约することができて、私达のウェブサイトの旅行记のページのhttp://chanyouji.com/trips/109123(小さく広告を打って、流量を持って)にとって:天気のデータを得る必要があって、写真のデータ、テキストデータなど、meta、keywordなどのseoデータも生成されますが、これらのコンテンツは他のダイナミックコンテンツと交差し、クリップキャッシュは複数に分けることができます.
- cache "trips/show/seo/#{@trip.fragment_cache_key}", :expires_in => 1.day do
title #{trip_name @trip}
meta name="description" content="..."
meta name="keywords" content="..."
body
div
...
- cache "trips/show/viewer/#{@trip.fragment_cache_key}", :expires_in => 1.day do
- @trip.eager_load_all
小贴士、tripオブジェクトにeagerを追加しました.load_allメソッド、キャッシュがヒットしていない場合、クエリー時にn+1の問題を回避します.
def eager_load_all
ActiveRecord::Associations::Preloader.new([self], {:trip_days => [:weather_station_data, :nodes => [:entry, :notes => [:photo, :video, :audio]]]}).run
end
テクニック1:条件付きクリップキャッシュとcaches_Actionとは異なり、railsが持参したクリップキャッシュは条件をサポートしていません.例えば、ユーザーにログインしていないでクリップキャッシュを使いたいと思っていますが、ログインユーザーが使用しないので、書くのが面倒です.helperを書き換えることができます.
def cache_if (condition, name = {}, cache_options = {}, &block)
if condition
cache(name, cache_options, &block)
else
yield
end
end
- cache_if !user_signed_in?, "xxx", :expires_in => 1.day do
テクニック2:関連オブジェクトの自動更新常使用オブジェクトupdate_atタイムスタンプはcache keyとして使用され、関連オブジェクトにtouchオプションを追加して、関連オブジェクトタイムスタンプを自動的に更新することができます.たとえば、記事コメントを更新または削除するときに、自動的に更新することができます.
class Article
has_many :comments
end
class Comment
belongs_to :article, :touch => true
end
5.データ照会キャッシュ
通常、WebアプリケーションのパフォーマンスのボトルネックはDB IOに現れ、データクエリーのキャッシュをしっかりと行い、データベースのクエリー回数を減らし、全体の応答時間を大幅に向上させることができます.データ照会キャッシュは2種類あります.A.同じリクエストサイクル内のキャッシュは、文章リストを表示する例を挙げて、文章タイトルと文章カテゴリを出力します.対応コードは以下の通りです.
# controller
def index
@articles = Article.first(10)
end
# view
- @articles.each do |article|
h1 = article.name
span = article.category.name
同様のsqlクエリが10件発生します.
SELECT `categories`.* FROM `categories` WHERE `categories`.`id` = ?
railsにはquery cache(https://github.com/rails/rails/blob/master/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb)が内蔵されており、同じリクエストサイクルでupdate/delete/insertの操作がなければ、同じsqlクエリーがキャッシュされ、文章カテゴリが同じであれば、本当にデータベースをクエリーするのは1回しかありません.
記事のカテゴリが異なる場合は、N+1クエリの問題(一般的なパフォーマンスボトルネック)が発生します.railsが推奨する解決策は、Eager Loading Associations(http://guides.rubyonrails.org/active_record_querying.html#eager-loading-associations)です.
def index
@articles = Article.includes(:category).first(10)
end
クエリ文は
SELECT `categories`.* FROM `categories` WHERE `categories`.`id` in (?,?,?...)
B.要求周期にまたがるキャッシュと要求周期キャッシュがもたらす性能の最適化は限られており、多くの場合、要求周期にまたがるキャッシュを使用して、User modelなどの一般的なデータをキャッシュする必要があります.active recordにとって、統一的なクエリーインタフェースを利用してfetch cacheを利用し、callbackを利用してexpire cacheを利用すると、実現しやすいです.そして、既存のgemが使えます.
例えばidentity_cache ( https://github.com/Shopify/identity_cache )
class User < ActiveRecord::Base
include IdentityCache
end
class Article < ActiveRecord::Base
include IdentityCache
cached_belongs_to :user
end
#
User.fetch(1)
Article.find(2).user
このgemの利点は、コード実装が簡単で、cache設定が柔軟で、拡張も容易であることであり、欠点は、異なるクエリーメソッド名(fetch)、および追加の関係定義が必要であることである.
データキャッシュのないアプリケーションにシームレスにキャッシュ機能を追加したい場合は、@hooopoのsecond_をお勧めします.level_cache ( https://github.com/hooopo/second_level_cache ) .
class User < ActiveRecord::Base
acts_as_cached(:version => 1, :expires_in => 1.week)
end
# find ,
User.find(1)
# belongs_to
Article.find(2).user
実装原理はactive record下位arel sql ast処理(https://github.com/hooopo/second_level_cache/blob/master/lib/second_level_cache/arel/wheres.rb)を拡張することであり、拡張が困難であり、少量のフィールドのみを取得するクエリーではキャッシュできないという利点がある.
6.データベース・キャッシュ
編集中
この6種類のキャッシュは,クライアントからサーバ側まで異なる位置に分布し,節約できる時間もちょうど多くから少ない順に並べられている.