RailsとHeroku、AWSでAwesomeな画像アップロードからのサムネイル作成

## はじめに
あのさぁ・・・Heroku使ってる？ **1 Applicationあたり1 dynoまで無料**なの素晴らしいよね！

- [30秒制限](https://devcenter.heroku.com/articles/request-timeout)や
- [USとEUにしかリージョンが無かった](https://devcenter.heroku.com/articles/regions)り
- [2 dyno目からは約$36/月](https://www.heroku.com/pricing#dyno-size)いったり
- [Heroku Postgresは1万行までの無料](https://www.heroku.com/pricing#postgres)で
- [内部記録領域は揮発性](https://devcenter.heroku.com/articles/dynos#ephemeral-filesystem)だけど

**無料**という言葉が全てを許す。確認だけのステージングアプリケーションだったら1 dynoでいけるし、Twitter BOTや簡単な定期処理、まだダイヤの原石であるアクセス数の少ないウェブサービスなんかには持ってこい！デプロイもAWS Elastic Beanstalkなんかに比べたらめがっさ速いです！（アーキテクチャ上当たり前ということは置いといて）

### 立ちはだかる「画像アップロードからのサムネイル作成」
ただやはり制限があると難しいことも生まれてくるのです。ここではその中でも **画像アップロードからのサムネイル作成** を取り扱います。Herokuにおける問題は

- **リージョンがUS**であることによる **アップロード時間の増大**
    - **30秒制限**による画像アップロード+サムネイル作成の **タイムアウト**
    - 上記による **前のリクエストの処理待ち**
- **内部記憶領域が揮発性**であることによる **画像（アップロード画像、サムネイル画像）の保存場所問題**

## RailsとHeroku、AWSでAwesomeな画像アップロードからのサムネイル作成
では早速本題に入りましょう。ここでは

1. ブラウザからの画像アップロード
2. （アップロードされた）元画像からのサムネイル画像作成
3. サムネイル画像の保存

の大きく三つに分けて私の現時点でのベストプラクティスをお話したいと思います。
色々とぐぐって組み合わせた上でのベストプラクティスなので、恐らくもっと良い方法も出てくると思います。そういった際はコメント等して頂けると嬉しいです。

### 1. ブラウザからの画像アップロード
Herokuへのアップロードは北米リージョンでありアップロード時間がかかる上、記憶領域が揮発性ですので更に他のストレージへの転送が必要になってきます。そうなると30秒制限によりタイムアウトになったり、利用者が多い場合はアップロードの順番待ちが行われ（[Request queueing](https://devcenter.heroku.com/articles/http-routing#request-queueing)を参照）アップロード時間が非常に長くなってしまう場合があります。
そこでS3へのダイレクトアップロードです。Herokuを通さずアップロードすることで、その部分の負荷をまるまる減らすことが出来ます。gemを以下に示します。

- [s3_direct_upload](https://github.com/waynehoover/s3_direct_upload)
- [s3_file_field](https://github.com/sheerun/s3_file_field)

これらはCORSを使うことでHeroku上のページからS3へのダイレクトアップロードを可能にしています。二つ目のs3_file_fieldは一つ目のs3_direct_uploadを機能を削減しながら使いやすくしたもので、既存のfile_fieldの代わりにしやすいです。（これはダイレクトアップロード後、アップロード先のURLが入ったtext inputを生成したりしてくれます）
ちなみにS3のリージョンは個人的にはユーザに近い方をお勧めします。つまり日本に住む人が対象だったら東京リージョンですね。と言うのも、日本にいる人が画像の保存や取得をするのに都合が良いからです。（当たり前ですが）もっともサムネイルを作成する時などHerokuから画像を取得や保存する時にはUSリージョンより遅くなってしまいますが、サムネイル作成よりも画像の保存や取得の比率の方が大きいので総合的に見て前者をお勧めしています。

### 2. （アップロードされた）元画像からのサムネイル画像作成
この分野では[Paperclip](https://github.com/thoughtbot/paperclip)や[carrierwave](https://github.com/carrierwaveuploader/carrierwave)などが有名ですが、今回のようにこれらのgemにあるアップロード機能を使わない場合はちょっと重厚長大です。
よってこの場合はon-the-flyで（サムネイルへのアクセス時に）サムネイルを作成でき、薄く軽い[Dragonfly](https://github.com/markevans/dragonfly)がお勧めです。元画像をS3のものに指定することが出来、もちろん1でアップロードされた元画像を指定することも可能です。またon-the-flyの利点としてサムネイルサイズが変更になったとしても、Paperclipの様にこれまでの元画像のサムネイルを明示的に再作成する必要がありません。サムネイルの作成はアクセスがあった時に行われます。

### 3. サムネイル画像の保存
2で作成するサムネイル画像ですが、アクセスの度に生成していたらレスポンスタイムが遅くなって仕方がないです。Dragonflyでは簡単にローカルにサムネイルのキャッシュを持つことが可能ですが、dynoのディスクは揮発性なので再起動したらキャッシュが消えてしまいます。
そこで[CloudFront](http://aws.amazon.com/jp/cloudfront/)を使いましょう。CloudFrontからHerokuへアクセスするように設定し、画像へのアクセスをCloudFrontに飛ばすことで、間に挟まりキャッシュとして機能します。proxyとして機能しキャッシュをしてくれていると考えると分かりやすいかもしれません。
Heroku側での設定はDragonflyに一行足すだけです。以下は一つ目がやり方でそれ以降は参考文献です。

- [Using a CDN](http://markevans.github.io/dragonfly/cache/#using-a-cdn)
- [Heroku + Assets + Dragnfly + Optimization + CDN](http://blog.29steps.co.uk/post/54207967944/heroku-assets-dragnfly-optimization-cdn)
- [Configuring rails 3.1 and dragonfly to use Herkou and Amazon CloudFront](https://gist.github.com/mbrictson/1160914)
- [Using Amazon CloudFront CDN with Rails](https://devcenter.heroku.com/articles/using-amazon-cloudfront-cdn-with-rails)
    - 最近の記事っぽい。今見つけた。図もあってCloudFrontの概念を理解するのに良いかもです。

ただCloudFrontが行うキャッシュの有効期限が不安だったので（短いと結局何度もサムネイル作成しちゃう）、関連する記事を備忘録的に残しておきます。ここらへんのベストプラクティスや検証結果かもんかもんです。
→今Dragonflyが生成するサムネイルのヘッダを見てみたら以下の様に書いてありますね。問題無いみたい？

```
Cache-Control:public, max-age=31536000
```

- [CloudFrontのキャッシュ時間(TTL)はどの程度なのか](http://dev.classmethod.jp/cloud/aws/cloudfront-ttl/)
- [How to cache files over 1MB with rack/cache on Heroku?](http://stackoverflow.com/questions/17656999/how-to-cache-files-over-1mb-with-rack-cache-on-heroku)
- [Specifying How Long Objects Stay in a CloudFront Edge Cache (Expiration)](http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Expiration.html)

CloudFrontの代わりに **S3にサムネイルを保存** して、サムネイルの作成有無をデータベースに保存するということも可能です。Dragonflyは色々と載っていてドキュメント素晴らしいですね。

- [Processing on-the-fly and serving remotely](http://markevans.github.io/dragonfly/cache/#processing-on-the-fly-and-serving-remotely)
- [Serving Remote Content with Dragonfly](http://blog.crowdint.com/2011/12/07/serving-remote-content-with-dragonfly.html)

## おわりに
Heroku最高！！！！！あとRailsというエコシステム最高！！！！！！！！