ボクココ

サービス開発を成功させるまでの歩み

Heroku の Fastly を使ったアセットの CDN 化

ども、@kimihom です。 Heroku Advent Calendar 2016 の 19日目を担当します。

f:id:cevid_cpp:20161220233310p:plain

正直に言おう。Rails アプリケーションの image, style, script は今まで Heroku 非推奨の asset_sync を使ってた。非推奨の理由だけど、 Heroku 的には S3 はそもそもファイル置き場であり、コンテンツ配信に最適化されたものではないというのと、Heroku デプロイに時間がかかり、同期に失敗した時の扱いなどの懸念点から、とのことである。とはいえ、当時は最も手軽で S3 だからそれなりに速く動いてくれる S3 に画像とかを置く方法はなかなか筋が通っていて気に入っていた。

ただ、サービスをより速く正確なものにするために重い腰を上げたってわけだ。実を言うと、以前 AWS の CloudFront を使って挑戦したのだが、その時に 謎のリダイレクトが走り、画像とかが全くキャッシュされず意味不明だったので戻したという辛い過去がある。そんな過去を知りたい方はこちら。

Heroku on Rails で asset_sync ではなく Cloudfront を利用する方法

そんな辛い過去があったので、今回は別の方法でしかも良さそうな Heroku アドオンがあり、そちらを使うことにした。

f:id:cevid_cpp:20161220230729p:plain

「おいおい $25 もかかるのかよ」と思った方。素直にページの戻るボタンを押していただきたい。そして CloudFront で自前で色々と調べて構築していただければ幸いだ。私が Fastly を気に入った点と、実際に CDN 化する上でハマったポイントなどを以下でご紹介する。

そもそもなぜ CDN

知ってる人は読み飛ばしていただきたいが、なぜ CDN かって話。 CDN は Contents Delivery Network の略で、ウェブ上にあるコンテンツを配信するのに最適化されたネットワークのことを言う。よく jQuery とか React.js などを読み込もうとした時に、script src="" で以下のような URL を指定したことはないだろうか。

jQuery CDN

これらは非常に高速に読み込んでくれるので、どこか適当な Web サーバーの公開フォルダにファイルを置いたのとは訳が違うほどに速い。最終的には、ユーザーの初期ロードのくるくるを最小限にしてくれるってのが CDN のメリットだ。CDN を使えば、ユーザーのアクセスに応じて最適なサーバーへ配置されたファイルをキャッシュとして読み込んでくれるため、一度 CDN 経由で読み込めばより良い Web の体験ができるようになることになる。

Fastly のメリット

CloudFront に比べて、 Fastly のメリットは何か。まずは強力なサポート体制にある。AWS なら自分で頑張って調べて解決しないといけないのは割と大変。また、 Fastly は CloudFront に比べて、言語の詳細まで解説した記事が提供されている。この2部構成の記事などは中々 Rails 界隈でも出回ることの少ない情報ではないだろうか。

Accelerating Rails, Part 1: Built-in Caching | Fastly

Accelerating Rails, Part 2: Dynamic HTTP Caching | Fastly

上記記事にはいろいろなキャッシュの方法があるけど、今回はひとまず アセットを CDN 化したいって目的だったので、それ以外のキャッシュ手法はとりあえず今後に置いておくことにした。早すぎる最適化は諸悪の根源だからね。

Fastly の特徴として、ファイル単位で CDN の再読み込みとか、一括クリアとかも簡単に行えるってのもメリットだそうだ。こういうのは開発中に地味に役立つ嬉しい機能だ。

Heroku on Rails with Fastly

さて、 Fastly を使ってみよう。まず大事なのが、Fastly の仕組み。こちらの画像は Heroku のドキュメント 拝借させていただいた。

f:id:cevid_cpp:20161220231934p:plain

こんな感じで間に CDN が入ってくれて、2回目以降は CDN から取ってこれるようにする。そうしたら Heroku へのアセットのアクセスは期限が切れるまでは最初の1回だけで済む。

設計の上で大事なのが、この CDN の ホスト名をどうするかという話。もし、*.-herokuapp-com.global.ssl.fastly.net ってホスト名が気にいらねーよ!って方は独自ドメインの CDN を扱うことになるが、それでさらに SSL にしたい場合はそれなりにお高い Fastly のプランにしなければできない。ここはまず確認しておこう。*.-herokuapp-com.global.ssl.fastly.net で大丈夫だ、問題ない。という場合には $25 のプランで SSL にあるアセットにも対応できる。本記事は、独自ドメインを使わない場合での想定。

さらに注意したいのが、フォントの扱いだ。これは CSS の url 経由で CDN -> Heroku へと渡ってくるので、CORS の設定が必要だ。Gemfile に gem 'rack-cors', :require => 'rack/cors' を追加し、config/application.rb に以下を追記しよう。

    config.middleware.insert_before ActionDispatch::Static, Rack::Cors do
      allow do
        origins '*'
        resource '/assets/*', :headers => :any, :methods => [:get, :options]
      end
    end

これでフォントの読み込みも CDN で可能になる。ここで注意したいのが、キャッシュを消さないと変化がないように見えるってことだ。しっかりと Chrome の設定から "キャッシュされた画像とファイル" の項目を削除しないと、変更したのに効いてない!みたいな事態に陥る。これはハマりやすいポイントなので注意だ。

そしてさらに注意したいのが、HTTPS の設定だ。デフォルトでアドオンを追加すると、 http のポート 80 が指定されている。これを https の 443 にしてあげないと、謎のリダイレクトが走ってうまく動かない問題が起きる (もしかして CloudFront で起きた問題もこれだった?)。 Fastly の Configure -> Hosts の Backends を Edit し、 ポートを 443 にすれば OK だ。

あと Settings の Default TTL を 15552000 くらいにして保存しておこう。そうそう、最後に Active ボタンを忘れずにね。

あとは Fastly のドキュメントを見ながら設定すれば詰まることはないだろう。間違いなく一番大事なのは、config/environments/production.rb

config.action_controller.asset_host = ENV['FASTLY_CDN_URL']

を追加するってこと。これだけでアセットの向き先が Fastly に向いてくれる。記事にある他のオプションも必要に応じて設定しておこう。

実際に使ってみて

いやはや、 これは速い。 S3 でもそれなりに早かったけど、Faslty のような CDN は明らかに速いってのが目に見えてわかる。それくらいの改善は見られるので、よりユーザー体験や速さを求める場合は是非使ってみていただきたい。

また、asset_sync などの余計な Gem の読み込みがなくなったので、デプロイ時間の削減だけでなく実行時のメモリ消費の削減にも役立った。 asset_sync って結構メモリ消費するのよね。これもなかなかありがたい。

https://github.com/twbs/bootstrap-sass のような Gem って全く意味ないなって思った。他の JS ライブラリ系の類似 Gem はたくさんあるんだけど、他の JavaScript ライブラリとの依存なんてほとんど管理していないし、最新版にあげたりとかもあまりメンテナンスされていないものがほとんどだからだ。こういうの使うくらいなら、 app/assets 内に vendor ディレクトリとか作って、それを読み込ませるようにした方がいいと感じて、今はそうしている。

とまぁ Fastly を使えばなかなかメリットが多かったので、これから Fastly で本番運用もやっていきたいという所存である。