Railsの画像アップロードを倍速にする方法

Shunsuke Sawada

ユーザーに画像をアップロードしてもらえるようなウェブサービスをつくる場合、
画像アップロード機能の実装はCarrierwaveでかなり簡単に実現できます。

wating

Carrierwaveの基本的な使い方はこちら。
Rails 超お手軽な画像アップローダー CarrierWave の使い方 | Workabroad.jp

ただ、たとえば10枚まで画像をアップできますよ、とした場合、
1つのフォームで10枚画像を添付して「送信」なんてやると、
送信完了までとても時間がかかります。
スマートフォンの画像解像度も昔にくらべたらかなり大きいし。。
ユーザーが送信完了まで待ってくれる気がしない。。。

といった時に助けてくれるGemがcarrierwave_backgrounder

lardawge/carrierwave_backgrounder · GitHub

まぁ倍速にするというか、
画像のアップロードをバックグランドで処理させるということなので、
画像アップロード自体が倍速になるわけではないですが。すみません。

ただ、アップロード作業なしに次の画面に遷移するので、
体感的には倍速どころかもっと早くなるはず。

同じようなGemにGarrierwave_directというものがありますが、
使ってみた結果、個人的にこちらがお気に入りです。

環境

Gem インストール

/Gemfile

1
2
追加
gem 'carrierwave_backgrounder'

後は $bundle install するだけで終了!、、とはいきません。
いろいろと準備がいるので、そのお話です。

Redisの準備

準備がなかなか大変なのです。

バックグラウンドプロセスを可能にするgemが必要です。
carrierwave_backgrounderは以下に対応。
Delayed Job, Resque, Sidekiq, SuckerPunch, Girl Friday, Qu, Queue Classic

今回はSidekiqを選びます。
そしてSidekiqを使うにはRedisというインラインデータベースを使います。

なので、まずはRedis。
公式サイトのまんまですが、インストールは下記。

1
2
3
4
5
$ cd お好きなディレクトリに
$ wget http://download.redis.io/releases/redis-2.6.16.tar.gz
$ tar xzf redis-2.6.16.tar.gz
$ cd redis-2.6.16
$ make

Redis起動

1
$ src/redis-server

configを指定してくれとのWarningが出るので、
一度control+Cで停止させてから、もう一度起動。

1
$ src/redis-server redis.conf

Sidekiqの準備

/Gemfile

1
2
3
4
5
# 追加
gem 'sidekiq'

- インストール -
$ bundle install

Sidekiqをテストしてみる。
/app/workers/sample_worker.rbを作成。

performを定義します。

1
2
3
4
5
6
7
8
9
10
11
12
class SampleWorker

  include Sidekiq::Worker
  sidekiq_options queue: :sample

  def perform(user_id)
    user = User.find(user_id)
    name = "userIDは#{user_id}だよ!"
    user.update_attribute(:name, name)
  end

end

Userモデルがあって、nameカラムがあると想定しています。

/app/controllers/users_controller.rb

1
2
3
4
5
6
7
8
9
10
 def create
  @user = User.new(user_params)
   if @user.save
     #これがトリガー
     SampleWorker.perform_async(@user.id)
     redirect_to @user
   else
     render 'new'
   end
 end

上の例だと、Userを新規作成した時に、
sample_workerが動いて、保存されたユーザー名を勝手に書き換えるという処理です。
まぁ実際にはこんなことしないでしょうけど、テストです。

Sidekiqを動かす

1
$ bundle exec sidekiq -q sample

-qオプションにsidekiq_options queue: :sample で付けたキュー名を渡します。
しばらくすると、
Booting Sidekiq 2.15.2 using redis://.......
とかなるはず。

で、先ほどつくったsample_workerが呼び出される処理、
ここでは、ユーザーの新規作成ですが、
ブラウザから実際に操作してみると、、、、

Workers::StoreAsset JID-d0371cf865719ce031d2262a INFO: start

と、バックグランドで処理が動き出したのがターミナルで確認できます。

Workers::StoreAsset JID-d0371cf865719ce031d2262a INFO: done

となったら、処理が終了しています。
実際に保存された名前を見てみると、、
nameが 「userIDは34だよ!」 みたいな名前になってるはず。

OK。準備完了。

Carrierwave_backgrounderを動かす

やっと、準備が整ったので、いよいよ画像のバックグランド処理。

/Gemfile

1
2
3
4
5
6
# 追加
gem 'carrierwave_backgrounder'

-- インストール --
$ bundle install
$ rails g carrierwave_backgrounder:install

/config/initializers/carrierwave_backgrounder.rb
というファイルができたはず。

今回はsidekiqなので、該当箇所のコメントアウトを外します。
carrierwave_backgrounder.rb

1
2
3
4
5
6
7
8
9
CarrierWave::Backgrounder.configure do |c|
  #.backend :delayed_job, queue: :carrierwave
  # c.backend :resque, queue: :carrierwave
  c.backend :sidekiq, queue: :carrierwave
  # c.backend :girl_friday, queue: :carrierwave
  # c.backend :sucker_punch, queue: :carrierwave
  # c.backend :qu, queue: :carrierwave
  # c.backend :qc
end

/app/uploaders/user_image_uploader.rb

1
2
3
4
5
6
class UserImageUploader < CarrierWave::Uploader::Base
  # - 追加 -
  include ::CarrierWave::Backgrounder::Delay
 .
 .
end

環境によって分ける場合は、こんなのとかでも。
/app/uploaders/user_image_uploader.rb

1
2
3
4
5
6
7
8
9
  # Amazon S3
  if Rails.env.production? or Rails.env.development?
    storage :fog
    include ::CarrierWave::Backgrounder::Delay
  end
  # Local strage
  if Rails.env.test?
    storage :file 
  end

モデルも変更します。
/app/models/users.rb

1
2
3
4
5
6
7
8
class User < ActiveRecord::Base
  # Image upload
  mount_uploader :image, UserImageUploader
  # - 追加 -
  store_in_background :image
 .
 .
end

Userモデルにカラムをひとつ追加します。

1
2
3
4
5
6
7
8
9
10
$ rails g migration AddImageTmpToUser image_tmp:string

確認
class AddImageTmpToUser < ActiveRecord::Migration
  def change
    add_column :users, :image_tmp, :string
  end
end

$ rake db:migrate

DONE!

Userを保存するコードをひとつもさわってませんが、これでOK。

あとはこのコマンドを実行するだけ。

1
$ bundle exec sidekiq -q carrierwave

Userを保存してみると、アップロード作業がスルーされて速攻で次の画面に行くかと。
すばらしい!

補足

現在、どんなキューがあるのか知りたい場合は
Sidekiqが素敵なインターフェイスを用意してくれている。

Gemfile

1
2
3
4
5
6
7
gem 'sidekiq'
  - 追加 -
gem 'slim', '>= 1.1.0'
gem 'sinatra', '>= 1.3.0', :require => nil

- インストール -
$ bundle install

config/routes

1
2
3
  - 追加 -
  require 'sidekiq/web'
  mount Sidekiq::Web => '/sidekiq'

ブラウザで、your_app.com/sidekiq にアクセスすると、
かっこいい画面で確認できるはず。

sidekiq

以上です。
おつかれさまでした。

41
Shunsuke Sawada

おすすめの記事

CakePHP 2.x JSヘルパーでajax通信(ajax helperは使わない)
20
Rails4でQiita投稿ボタンをつくった
18
紙のデザイナーがウェブ開発できるようになるまでに必要なこと
451