rails4とbootstrapを使ったウェブアプリケーションを作って、そのなかで表示を速くするために行った事をざっと書いていきます。
ActiveRecordのincludeやjoinに関する全般
これについては色々な所で書かれている通りです。ログ見ながらSQLを最適化させる形でActiveRecordの最適化を行いました。
link_to
controllerとactionを指定した書き方の方がすっきりしますが、url_forを使ってroutingが発生すると遅いです。
ダサいですが、べた書きにしました。
link_to "link", "/action/#{m.id}"
参考URL:
http://railsguides.net/rails-url-for-helper-can-be-slow/
render :partial
仕組み的にそういうものだよね、と考えることも出来ますが、例えばカレンダーのように同じviewをネストして何回も呼び出すとさすがに遅いです。
何回も呼び出す場合は、def_erb_methodを使うと速くなります。
def_erb_method('render_plan_summary(plan)', "#{Rails.root}/app/views/venue/_plan_summary.erb")
参考URL:
パフォーマンス・チューニング3:erbメソッド化を参照
http://www.ohmyenter.com/?p=105
##paperclip#url
画像のアップロードにpaperclip+S3を使っている場合に限定される話になります。標準のurlメソッドを使うとすっきり書けますが、
image_tag(product.pictures.first.data.url(:gallery))
遅いのでこれもダサいですがべた書きにしました。
image_tag("http://bucketname.s3.amazonaws.com/products/#{product.pictures.first.id}/gallery.jpg?1325844462"
lazyな画像の読み込み
bootstrapを使うとPC/Tablet/Mobileで別々のサイズの画像を読み込むために、imgタグの量が多くなります。
さらにページを読み込んだ時点で全画像を読み込むとページの描画が遅くなるので、jQueryのlazyプラグインを使いました。
注意点として、ちゃんとサイズを指定してあげないと画像読み込み前と後でサイズが変更されてしまいます。ページ位置がずれるように感じてしまうので、ちゃんと画像サイズは指定してあげましょう。
デバイス別にimage_tagの出力
imgタグのsrcに書かれたURLはブラウザが自動で読み込まれます。lazyを付けていても表示位置が来たら読み込まれます。display:noneしていても読み込みます。
良くある解決方法はbackground-imageにする事ですが、この方法は複雑に感じたのでデバイスの種類別に出力するimgタグを分ける方法にしました。
これに伴ってキャッシュもデバイスの種類別に分ける必要が出てきます。
image_tagを以下のようにデバイスによっては何も出力しない。
jpmobileを使っています。
https://github.com/jpmobile/jpmobile
def devise_image_tag(path, options = {})
if options[:devise].present?
if request.mobile? || request.smart_phone?
return "" if options[:devise].to_s != "mobile"
else
return "" if options[:devise].to_s != "full"
end
end
if include_lazy?(options[:class])
my_image_tag(empty_image_path, options.merge("data-original" => path)).html_safe
else
my_image_tag(path, options).html_safe
end
end
image_tagはそのまま使うと遅いので、自力でこんな感じに書き換えました。
def my_image_tag(path, options)
options[:src] = path if path.present?
if options[:size].present?
(options[:width], options[:height]) = options[:size].split("x")
options.delete(:size)
if options[:style].blank?
options[:style] = "width:#{options[:width]}px; height:#{options[:height]}px;"
end
end
tag(:img, options).html_safe
end
imgタグの出力でlazyを使うことが多いので、classにlazyが指定されているかをこんな感じで確認。
def include_lazy?(class_string)
(class_string || "").split(/\s+/).any?{|v| v == "lazy"}
end
デバイス別にキャッシュを分ける
applicationコントローラーの中でデバイス種類を取得しておきます。
class ApplicationController < ActionController::Base
before_action :fetch_devise
def fetch_devise
@devise_type = ((request.mobile? || request.smart_phone?) ? "mobile" : "full")
end
end
cacheに使うprefixを準備して
def cache_prefix
"#{@devise_type}_"
end
こんな感じでviewのcacheで使います。出力されるimgタグがデバイスの種類で異なってくるので、キャッシュされるHTMLも分ける必要があります。
<% cache(cache_prefix + "my_footer", :expires_in => 5.minutes) do %>
fast_blank
railsのblank?を速くするgemです。
escape_utils
escape処理を速くするgemです。
インフラ面/リソース面で行った事
リバースプロキシ
アプリケーションサーバーはpassenger、その手前にapacheでリバースプロキシを作ってリダイレクト等のアプリケーションサーバーにやらせるメリットが無いものを処理するようにしました。
passengerはforkして動くのでリソースファイルを捌くようなものは全てworkerスレッドで動作するリバースプロキシ側に任せました。
CDN
Amazon CloudFrontにjsやcssやimageやfontを置きました。rails本体にリソースをCDNに配置する仕組みがなかったので、ここはデプロイするスクリプトをちくちくと自作です。
画像の圧縮
サーバーサイドでjpegを軽量化(jpeg-miniみたいな事)
http://qiita.com/tkosuga@github/items/a2dbe2f8dfb7904d5fc6
pngについてはpngquantを使って軽量化しました。
http://pngquant.org/
cssスプライト
SpriteFactoryを使ってさくっとファイルまとめました。
https://github.com/jakesgordon/sprite-factory
一般的なパイプライン処理
jsやcssをそれぞれ1つのファイルにまとめてgzipにしてA3に転送してpermissionの設定をしたりと一般的なパイプライン処理を行っています。
CDNに配置したりする関係があって、rails標準のpipelineを使わず最小限に実現できるものを自作しました。標準でライブラリが全部そろっているので、Asset pipelineで悩んだり困ったりしているなら、これについてはもしかすると自作した方が良いかも知れません。
やってもあんまり速くならなかったもの
HTMLのスリム化
HTMLをスリムにすれば速くなると思ってhtmlcompressorを試したのですが、思った以上に処理に時間がかかるので止めました。生成したHTMLをキャッシュする前提ですが、それでも初回の生成が思った以上に遅かったです。