-
Notifications
You must be signed in to change notification settings - Fork 9
building a rails 3 application with the memcache addon
webアプリケーションへキャッシュを追加することにより、劇的にパフォーマンスを改善することが出来ます。 複雑なDBへの問い合わせ結果や、高度な計算処理、外部リソースへの時間の要するコール、これらの結果はシンプルなキー値へアーカイブすることが可能です。キー値へは高速なO(1)探索を経由してアクセス可能です。
このチュートリアルでは、シンプルなRails 3.2アプリケーションをクリエートし、Herokuへデプロイ、そして高度なクエリーの結果をキャッシュするためにMemcache Add-onを使用することで学んで行きます。
サンプルコードは、GitHubに上がっています。[demo Rails application](https://github.com/mattmanning/memcache-example)
スケルトンの作成のため、rails
コマンドを使います:
:::term
$ rails new memcache-example
$ cd memcache-example/
Gemfile内の下記個所を変更して下さい:
:::ruby
gem 'sqlite3'
を
:::ruby
group :development do
gem 'sqlite3'
end
group :production do
gem 'pg'
end
へ変更して下さい。
こうすることで、本番環境のアプリが、Postgresのデータベースを使用することを保証します。
次に以下のコマンドを実行して下さい。:
:::term
$ bundle install --without production
このコマンドは、明記されたgemsのみをインストールするために使います。そしてGemfile.lock
というファイルが作成されます。
--without production
オプションは、pgのgemがローカルにインストールされることを防ぎます。
このプロジェクトをGitのリポジトリに作成し、変更をコミットして下さい。:
:::term
$ git init
$ git add .
$ git commit -m "first commit"
Heroku上に新しいアプリケーションを作成するために、heroku
コマンドを使用して下さい:
:::term
$ heroku create --stack cedar
その後で、Herokuへデプロイして下さい。:
:::term
$ git push heroku master
Memcacheこの記事にある通り、
Memcacheアドオン以外に、dalli
というgemもインストールが必要です。:
ターミナルで以下を実行します。:
:::term
$ heroku addons:add memcache:5mb
dalli
(memcacheクライアントのライブラリ)をインクルードするために、Gemfileを変更して下さい。:
:::ruby
gem 'dalli'
dalli
により提供されるキャッシュの保管を利用するために、デフォルトで使用されるRailsのキャッシュを設定して下さい。
config/environments/production.rb
にあるファイルに、下記を含めます。
:::ruby
config.cache_store = :dalli_store
このサンプルアプリがどのように動作するかを簡単に検証するために、元々備わっているキャッシュの機能を一時的にオフに して下さい。下記の箇所をコメント化します。:
:::ruby
# config.action_controller.perform_caching = true
名前とemailアドレスだけのシンプルなテーブルへ、データの格納と参照を行うために、 Railsのscaffold generatorを使用して下さい。:
:::term
$ rails g scaffold contact name:string email:string
$ rake db:migrate
rootのルートへcontacts#index
をセットするために、config/routes.rb
を下記のように編集して下さい。
:::ruby
root :to => 'contacts#index'
それから、public/index.html
を削除して下さい。
変更をコミットし、Herokuへプッシュして下さい。下記のコマンドを使用し、リモートのデータベースをマイグレートして下さい。:
:::term
$ heroku run rake db:migrate
contactsのリストを確認するため、heroku open
コマンドを使用すれば、Heroku上に作成したアプリを確認することが出来るでしょう。
"New Contact"のリンクを押し、いくつかのレコードを作成して下さい。
現在のContactsController
内のコードは、このような感じかと思います。:
:::ruby
def index
@contacts = Contact.all
respond_to do |format|
format.html # index.html.erb
format.json { render json: @contacts }
end
end
/contacts
がリクエストされる度に、index
メソッドが実行され、contactsテーブル内の全てのレコードをフェッチする
クエリーが走ります。
テーブルが小規模で、リクエストされる量も少ない場合、あまり問題にはなりませんが、
データ量が増え、ユーザー数も増えた場合、アプリケーションのパーフォーマンスへ与えるインパクトは大きくなります。
Contact.all
の結果をキャッシュしてみましょう。こうすることで、同一のページを訪問する度に、データベースへの
問い合わせが走ることはありません。
app/models/contact.rb
に下記のメソッドを追加して下さい。:
:::ruby
def self.all_cached
Rails.cache.fetch('Contact.all') { all }
end
app/controllers/contacts_controller.rb
内の
:::ruby
@contacts = Contact.all
を下記に変更して下さい。
:::ruby
@contacts = Contact.all_cached
さらに、indexのページに統計情報を表示させてみましょう。app/controllers/contacts_controller.rb
内の
index
メソッドへ、下記の行を追加して下さい。:
:::ruby
@stats = Rails.cache.stats.first.last
それから、app/views/contacts/index.html.erb
内の一番最終行に、下記のマークアップを追加して下さい。:
:::html
<h1>Cache Stats</h1>
<table>
<tr>
<th>Metric</th>
<th>Value</th>
</tr>
<tr>
<td>Cache hits:</td>
<td><%= @stats['get_hits'] %></td>
</tr>
<tr>
<td>Cache misses:</td>
<td><%= @stats['get_misses'] %></td>
</tr>
<tr>
<td>Cache flushes:</td>
<td><%= @stats['cmd_flush'] %></td>
</tr>
</table>
修正をコミットし、Herokuへプッシュして下さい。/contacts
のページをリフレッシュすると、"Cache misses: 1"と
表示されるでしょう。これは、'Contact.all'キー値でフェッチしようと試みたものの、値が存在しなかったことを意味しています。
再度、ページのリフレッシュを行って下さい。そうすると、"Cache hits: 1"と表示されるでしょう。
今回は、先ほどのリクエストにより値が格納されたため、'Contact.all'のキー値が存在することとなります。
Herokuのコンソールからキャッシュをクリアしてみると、再びこの効果を確認することが出来ます。:
:::term
$ heroku run console
>> Rails.cache.clear
Contact.all
がキャッシュされるようになりましたが、テーブルの値が変更された時、何が起こるでしょうか?
新たにcontactのレコードを追加し、リストのページに戻ってみて下さい。新たに追加したはずのcontactが表示されないでしょう。
これは、Contact.all
がキャッシュされるので、古いテーブルの値が格納され、表示されていることが原因となります。
テーブルの値が変更された時に、キャッシュを失効させる方法が必要となります。これは、Contact
モデルに
フィルターを追加することで対応可能となります。
下記のコードをapp/models/contact.rb
に追加して下さい。:
:::ruby
after_save :expire_contact_all_cache
after_destroy :expire_contact_all_cache
def expire_contact_all_cache
Rails.cache.delete('Contact.all')
end
これらの修正をコミットし、Herokuへプッシュして下さい。テーブルへcontactを格納(追加または更新)するか、contactを
削除する毎に、Contact.all
のキャッシュのキー値が削除されます。テーブルの値を変更し、/contacts
のページへ戻る度に、
"Cache misses"のカウントが1ずつ増加しているのが確認出来るでしょう。
上記の例では、キャッシュのフェッチと失効について、詳細な説明を行っています。便利なことに、Railsは、このような機能の
ほとんどをビルトインで提供してくれます。例えば、もしshow
アクションの結果をキャッシュしたいのであれば、
app/controllers/contacts_controller.rb
内に下記の行を追加して下さい。:
:::ruby
caches_action :show
適切にキャッシュを失効させるために、contacts_controller.rb
内のupdate
とdestroy
メソッドの両方へ
下記の行を追加して下さい。
:::ruby
expire_action :action => :show
Action Cachingは、ページキャッシュと同じような形で、オブジェクトとビューをキャッシュしますが、 リクエストがアプリケーションスタックにヒットするので、認証のようなものにbeforeフィルターを適用することが出来ます。
さらに理解を深めるために、Rails Guide on Cachingを 読んで下さい。Railsにビルトインされているアクション、フラグメント等へのキャッシュ機能に関し、素晴らしい情報が記載されています。
このチュートリアルで作成されたアプリケーションの全ソースは、GitHub上から、自由にダウンロード可能となっています。