Herokuで作るFacebookアプリ

第12回Herokuで非同期処理を実装しよう

はじめに

前回は、Herokuでファイルアップロードを行う方法をご紹介しました。gemを利用してAmazon S3にファイルをアップロードすることで非常に簡単にファイルアップロードが実現できることがわかったと思います。今回は、前回のファイルアップロードと同様に、Webサービスを提供する際によく利用される非同期の処理について紹介します。

Delyed::JobとCron

Railsでの非同期処理は、登録したジョブを逐次処理する方式と定期的に処理を実行する方式があります。

1つ目の逐次処理は、本来はリクエスト時に処理すべきジョブを実行に時間がかかってしまうため、リクエストの処理とは別のプロセスで実行することを指しています。画像をアップロードした際に、リサイズを実施する処理が重いため非同期にするというのが例です。これをRailsで実現するためには、いくつかのgemが用意されています。Herokuでは、標準的にDelayed::Jobというgemを利用して実現します。

もう1つの定期処理は、定期的に実行するジョブを実行することを指しています。一般的には、これを非同期処理とは呼ばず、バッチ処理と呼ばれるかもしれません。リクエスト時には実行しないのでここでは非同期と呼びました。こちらは、Linuxサーバではcronを利用して実現されることが一般的です。HerokuでもアドオンとしてCronの実行をサポートしています。無料では、毎日1回の処理の実行が利用できます。有料アドオンとして、毎時の処理の実行もサポートします。$5/月の料金がかかります。

今回は、こちらの2つの非同期の処理を実際にどのようにHerokuで実現できるのかを紹介します。

Delayed::Job

まずは、Delayed::Jobでの逐次処理の実現方法です。以下に導入方法を解説します。

gemをインストールします。Gemfileに以下の行を追加します。

gem "delayed_job"

gemをインストールするために、bundleコマンドを実行します。

$ bundle

gemのインストールが完了すると以下のgenerationが利用できるようになりますので、generationコマンドを利用してマイグレーションファイルを生成します。

$ rails g delayed_job

ジョブを管理するためのテーブルが生成するため、マイグレーションを実行します。

$ rake db:migrate

これで、Delayed::Jobを利用する準備は完了です。実際にソースコードを編集して非同期処理にしてみます。ここでは、Facebookにマンガを登録したことを投稿する処理を非同期にします。APIのレスポンスが遅い場合に、自分のサービスのレスポンスも一緒に遅くならないようにします。MangasController(app/controllers/mangas_controller.rb)を以下のように編集します。

   begin
     @manga.save!
-    @manga.post_link_to_facebook
+    @manga.delay.post_link_to_facebook
     redirect_to mangas_url, :notice => "Successfully created manga."
   rescue ActiveRecord::RecordInvalid => e

delayというメソッドを実行するメソッドの前に挟むことでその処理を非同期処理とすることができます。非常に手軽ですね。必要な処理の中でユーザの画面表示とは関係なく時間がかかる処理などはこのように非同期にすることで、ユーザへのレスポンスを早くすることができます。

以下の2つの図は、Railsの実行ログです。もともとは上の図のようにレスポンスを返す処理の中で実行されていたFacebookに投稿する処理([POST/feed] の行)が、下の図では代わりにdelayed_jobsテーブルへのINSERTに変更されています。

図1 非同期処理前
図1 非同期処理前
図2 非同期処理後
図2 非同期処理後

このようにDBにジョブが登録された形になります。この登録された処理を実行するためには、Webサービスとは別のプロセスを動作させる必要があります。開発環境では、以下のコマンドを発行するとジョブが実行されます。

$ rake jobs:start

このコマンドを発行すると、プロセスが立ち上がってどんどんジョブを処理していきます。

Heroku本体ではこの新しいプロセスをWorkerと呼びます。WorkerはWebのプロセス dyno と同様プロセス数に応じた価格が設定されていて1プロセスから有料になっています。値段もdynoと同じく1Worker毎に $0.05/h となっています。以下のコマンドでWorkerを1プロセス起動させることができます。

$ heroku workers 1

以上のように、Delayed::Jobを用いた非同期の処理は実現ができます。変更のコミットはこちらです。

Cron

次に、定期処理をHerokuのCron Addonを利用する方法を紹介します。ここでは登録されたマンガの数や、登録したユーザ数をGoogleSpreadSheetに保存して記録するという仕組みを入れるためにCronを利用します。これは毎日1回を想定していますので、DailyのCron Addonを導入するため無料です。以下のコマンドでAddonを登録します。

$ heroku addons:add cron:daily

このAddonを設定するとHeroku側が定期的に以下のコマンドを発行するようになります。

$ rake cron

このようにrakeコマンドを通して処理を実行します。履歴を保存するためにrakeタスクを作成します。

lib/tasks/cron.rake
desc "This task is called by the Heroku cron add-on"
task :cron => :environment do
  # log size of record for each model to Google SpreadSheet
  opt = [User.count,
         Manga.count]
  LogGss.update(opt)
end

このようにタスクの名前を cron として置くと実行されるようになります。これらの変更はこちらのコミットになっています。

定期処理はDelayed::Jobに比べると非常に簡単ですね。もし、毎時実行したい処理と組み合わせる場合は、このタスクの中に時間を判定する処理を入れるなどして組み合わせるようです。

まとめ

Herokuで、WebサーバのプロセスであるDyno以外で非同期に処理を実行する方法を2通り紹介しました。一般的なWebサービスでよく利用される非同期処理もHerokuでの設定方法を知っていただけたかと思います。決して、Heroku専用の仕組みではないのでRailsを利用している人にとっては自前のサーバでやっていることと同じかもしれません。これは、逆に言うと一般的な仕組みなのでこれを導入することでもHerokuにロックインされるようなことはないのです。ここでもHerokuの特徴であるオープンであることが実現されています。

おすすめ記事

記事・ニュース一覧