ども、マイケル(日本人)です。 調子がいいのでブログ連投してみます。 carrierwaveでめっちゃはまったのでその話を書きます。 carrierwave便利なんですがちょっといじるとすぐはまりますね。。。
やりたいこと
- carrierwaveのキャッシュファイルはlocalに、実画像はS3に置く(その際画像はjpgに変換)
- nginxで動的にサムネイル生成してcloudFrontでキャッシュさせたものをアプリ側で読み込む
ちなみにnginx等のインフラ周りの設定については書きません。 なぜならインフラ構築したのは僕じゃないから(`・ω・´) うちのスーパーインフラエンジニア様が3秒でやってくれました、もしかしたらブログに書いてくれるかもしれません。
画像UPLOAD実装
んでは早速実装していきましょう。 まずはドキュメントみてS3に上げる設定をしてください。 あと画像のリサイズ等にminimagick使うのでこの辺読んで設定してください
// avatar_uploader.rb(ファイル名はドキュメントに合わせてあります)
class AvatarUploader < CarrierWave::Uploader::Base
include CarrierWave::MiniMagick
process :resize_to_limit => [600, 600] # ファイルのサイズを600*600に
process :convert => "jpg" # jpgに変換してたもれ
version :original do # 一応オリジナルも1200*1200で保存しておく
process :resize_to_limit => [1200, 1200]
process :convert => "jpg"
end
# 拡張子はjpgに変換してるからjpg固定
def filename
"#{secure_token}.jpg" if original_filename.present?
end
protected
# cloudfrontでキャッシュするのでファイル名を毎回動的にユニークな物で生成
def secure_token
var = :"@#{mounted_as}_secure_token"
model.instance_variable_get(var) or model.instance_variable_set(var, SecureRandom.uuid)
end
end
この設定を書くことで以下のことを実現できます。
- carrierwaveのキャッシュファイルはlocalに保存(これがデフォなので特に設定必要なし)
- 600x600と1200x1200の画像をjpgに変換してS3にUP
- ファイル名は毎回動的に生成することでcloudfrontのキャッシュ更新(同じ名前の画像とか来るとcloudfrontのキャッシュを見て画像更新されてないやないか(´Д`)ってなるからね)
ちなみにcarrierwaveのキャッシュファイルはvalidationエラーに引っかかった時などにフォームに表示する画像に使われます。 validationを通って無事にレコードが保存されるとS3に画像がUPされます。 carrierwaveのキャッシュファイルもS3に置くことが出来ますが、今回UPする画像が多く通信コストが馬鹿にならないので、あえてlocalに保存してます。 carrierwaveのキャッシュファイルもS3に置く方法はこちらが参考になります。 CarrierwaveでS3にアップロードさせるとき、キャッシュもS3に置く
画像読み込み実装
今回nginxで画像を動的にリサイズします、リサイズした画像のurl仕様はこんな感じ http://cloudfront_domain/resize/size/ファイル名
sizeには120x120等の画像サイズが入ります。
// carrierwave.rb
CarrierWave.configure do |config|
config.asset_host = "cloudfront_domain"
end
// avatar_uploader.rb
class AvatarUploader < CarrierWave::Uploader::Base
# urlをoverrideしてsize渡せるようにする
def url(size = nil)
if size.nil?
super
elsif current_path.present?
# sizeが渡ってきた場合はそこのサイズの画像見る
return asset_host + "/resize/" + size + "/" + current_path
end
end
end
これで画像参照先はcloudfrontになります。 またリサイズした画像を参照するときは@model.image.url("120x120")とかでそのサイズの画像を読んでくれます。 いやーcarrierwaveさん便利ですわ。 しかし、ここまで来て僕の場合いくつか問題が発生しました。
- 画像UPLOADが異常に重い
- validationエラー発生した時に画像表示されない
- キャッシュフォルダが消えずに残ってる
画像UPLOADが異常に重い
今回UPする画像が多いのですが(MAX20枚)それでもUPLOADに異常に時間がかかりました。10枚で50秒とか。 newrelicで見るとs3との通信にもそれなりに時間かかっているのですが何よりrubyでの処理に異様に時間を取られていました。 インフラエンジニア様にも見てもらったんですがimageMagickが異常に呼ばれているとのこと。 なんでやーと思って、最小構成のrailsプロジェクト作って試したりしましたが解決せず。 もしかしてMiniMagickが怪しいんじゃないかと思い、試しにRMagickに変えたら直りました。 MiniMagickは最小機能で動作が軽いとか書いてあったので採用したのですがまさかの裏目に。。 とりあえずMiniMagick使ってて画像処理に異常に時間取られてる人はRMagickへの変更試してみてください。
validationエラー発生した時に画像表示されない
これ原因は単純でvalidationエラー出た時もcloudfront見に行ってるからですね。 validationエラーが発生した時はlocalのキャッシュを見に行くようにしてあげます。
// avatar_uploader.rb
class AvatarUploader < CarrierWave::Uploader::Base
# キャッシュ先のフォルダを指定
def cache_dir
"public/uploads/tmp"
end
# キャッシュファイルがある場合はキャッシュファイルのURLを返すメソッドを作成
def cdn_or_cache_url
if cached?
"/uploads/tmp/" + cache_name
else
url
end
end
end
これで後はhtml側に image_tag @model.image.cdn_or_cache_url とか書いておけばキャッシュがあるときはlocalのキャッシュファイルを見に行くようになります。
キャッシュフォルダが消えずに残ってる
// avatar_uploader.rb
class AvatarUploader < CarrierWave::Uploader::Base
before :store, :remember_cache_id
after :store, :delete_tmp_dir
#キャッシュIDを保存しとく
def remember_cache_id(new_file)
@cache_id_was = cache_id
end
def delete_tmp_dir(new_file)
if @cache_id_was.present? && @cache_id_was =~ /\A[\d]{10}\-[\d]{5}\-[\d]{4}\z/
# ディレクトリ消すでー
FileUtils.rm_rf(File.join(root, cache_dir, @cache_id_was))
end
end
end
これはここに書いてあったのほぼコピペですね。 キャッシュファイル自体は消してくれるんですがなぜかディレクトリが残って気持ちわるい状態だったので collback使って消してあげてます。