Shoryuken + SQSで非同期処理にした


シーエー・アドバンス Advent Calendar 2020 - Qiita 18日目の記事です。

Shoryuken導入の経緯

数万件のCSVデータを登録する機能を作成しましたが、処理に時間がかかってしまい処理が完了する前にタイムアウトしてしまう事象が発生しました。
タイムアウトを延長も検討しましたが付け焼き刃的対応であるため、数万件登録でも対応できるよう非同期処理できる機能が必要でした。

検討した処理の流れは以下となります。

  • SPA画面よりCSVをアップロードした際、S3に保存してキューをSQSに登録する
  • Workerにてキューを取得しバックグラウンドでS3からCSVをダウンロードしてCSV内のデータをDBに登録する

Shoryukenとは

AWS SQSと連動したActive Job Worker。
sidekiqが有名みたいですが、そちらがRedisをqueueStoreとしているのに対し、ShoryukenはSQSをqueueStoreとしています。
最初Sidekiqの導入を検討しましたが、耐障害性の高いSQSを使用したかったため他にもないと探していた際に目的にあったShoryukenを発見。即導入に至りました。

キューの作成

SQSにあらかじめキューを作成しておきます。

導入

Gemfile
gem 'shoryuken'

bundle install します

$ bundle install

設定ファイルは以下になります。
1処理の件数が多いため、スレッド数は控えめにしました。

config/shoryuken.yml
concurrency: 5
delay: 30
require: /usr/src/app/app/workers
pidfile: /usr/src/app/tmp/pids/shoryuken.pid
queues:
  - sqs_queue_sample

メッセージを取得するサーバー設定を作成します。
今回、キューの取得のみShoryukenを使用しますが、キュー登録の場合は、configure_client を定義すると、Shoryuken経由でキューの登録が行えます。

workerはECS fargateでコンテナ作成するため、ログはCloudwatch Logsで確認できるよう標準出力するようにしてます。

config/initializers/shoryuken.rb
Shoryuken.configure_server do |config|
  config.sqs_client = Aws::SQS::Client.new(
    region: "ap-northeast-1",
    access_key_id: ENV['AWS_ACCESS_KEY_ID'],
    secret_access_key: ENV['AWS_SECRET_ACCESS_KEY'],
    verify_checksums: false
  )
  Rails.logger = Shoryuken::Logging.logger
  Rails.logger.level = :info
end

Shoryuken::Logging.initialize_logger(STDOUT)

workerのメイン処理を実装します。
キューにメッセージが登録されるとperform メソッドが実行され、メッセージが受け取れます。
あとは、CSVデータの登録処理を書いていきます。

workers/do_worker.rb
class doWorker
  include Shoryuken::Worker

  shoryuken_options queue: 'sqs_queue_sample', auto_delete: true, body_parser: json

  def perform(sqs_msg, body)
    (ここにworker処理を書く)
  end
end

あとは、下記コマンドで起動させればOKです。
コンテナで運用する場合は、dockerfileのCMDに下記コマンドを追加でworkerとして起動します。

$ bin/bundle exec shoryuken -R -C config/shoryuken.yml

導入後

CSV登録処理をバックグラウンド化したことで、CSVレコード数でタイムアウトせず、
またSQSとShoryukenでスレッド化することで過剰な負荷がかかることがなくなりました。
稼働させて1ヶ月弱ですが、不具合などは発生していません。

gemモジュールを使用することでサクッと実装できました。
開発環境ではSQSの代わりにFakeSQSを実装したかったのですが、時間的都合で見送りとなりました。
開発用SQSキューを作成していますが、料金は発生してしまうので、FakeSQSコンテナで開発を行えるようにするつもりです。

参考