はじめに
この記事はLCL Advent Calendar 2021 - 4日目です。
バックエンドエンジニアの星野です。このアドベントカレンダー同じ人しかいないって?気のせいです。
LCLのバッチジョブ実行基盤解説記事の最後のエントリになります。 最終日はSQSとActive Jobについてです。この記事ではActive Jobそのものについては解説しませんのでRailsガイドを適宜参照してください。
Ruby on Railsの非同期処理でActive Jobを使う場合はアダプターを選択する必要があります。
SidekiqやResqueなどRedisをバックエンドにしたアダプターが人気がありますがRedisの管理をしなければならず、シンプルにperform_later
したいだけの要件に対して大掛かりになりすぎることがあります。*1
そこでフルマネージド型のメッセージキューイングサービスであるAmazon SQSをActive Jobのアダプターとして使うことで運用の手軽さと可用性の両方を手に入れます。
LCLではActive JobをSQSのアダプターとして使うためにaws-sdk-railsを選択しました。 類似のgemとしてShoryukenがありますが、aws-sdk-railsのコードベースが小さく処理が追いやすいため扱いやすいと感じています。
設定方法
SQSキューの作成
はじめにSQSキューを作成します。Terraformで作成する場合はこれだけです。
resource "aws_sqs_queue" "active_job" { name = "active-job" redrive_policy = jsonencode({ deadLetterTargetArn = aws_sqs_queue.active_job_dlq.arn maxReceiveCount = 5 }) }
redrive_policy
でリトライ回数とエラー時のDLQは設定しておくと役に立つでしょう。
SQSを設定する際に調整が必要になることの多いvisibility_timeout_seconds
やreceive_wait_time_seconds
はaws-sdk-railsと内部で使用しているaws-sdk-rubyでデフォルト値が設定されているので不要です。
Railsの設定
動かすために必要な最低限の設定はとてもシンプルでREADMEの通りに設定すれば難しいところはありません。
aws-sdk-railsをGemfileに追加してqueue_adapterで:amazon_sqs
を選択します。
# config/application.rb module YourApp class Application < Rails::Application config.active_job.queue_adapter = :amazon_sqs end end
作成したSQSキューをconfigに書きます。ハードコードせずに環境変数でセットするのも良いでしょう。
# config/aws_sqs_active_job.yml queues: default: 'https://sqs.ap-northeast-1.amazonaws.com/123456789012/active-job'
環境によってはSQSクライアントも設定が必要になるのでconfigurationの項を見ながら設定しましょう。
SQSメッセージを取得するためのIAMポリシー
Active Jobのプロセスに必要な最小のIAMポリシーは次の通りです。 こちらはドキュメントに記載がありませんでしたがgemが呼び出しているメソッドを調査して絞り込みました。もし不足している権限がありましたら指摘いただけると助かります。
{ "Version": "2012-10-17", "Statement": [ { "Sid": "1", "Effect": "Allow", "Action": [ "sqs:ReceiveMessage", "sqs:DeleteMessageBatch", "sqs:DeleteMessage", "sqs:ChangeMessageVisibility" ], "Resource": "arn:aws:sqs:ap-northeast-1:123456789012/active-job" } ] }
あとはEC2インスタンスやECSタスクで上記IAMポリシーをアタッチしたIAMロールを設定してaws_sqs_active_jobを起動すれば開始できます。
bundle exec aws_sqs_active_job --queue default
LCLではFargate Spotのみで構成したECSサービスをオートスケールしながらプロセスを起動しています。 SQSキューにリトライ機構が備わっているので、処理中にSpotの中断やスケールインによるエラーの発生を気にする事なく安価に高いパフォーマンスを発揮しています。
Active Jobを利用する
設定が完了したらRailsからはperform_later()
を呼び出すだけです。この時呼び出し側はsqs:sendMessage
のIAMポリシーが必要です
AwesomeJob.perform_later(record)
また、SQSにsendMessageすることができれば送信元はRailsである必要がないためAWS CLIからActive Jobを実行することも可能です。
SQSに送るJSONをActiveJob::CoreのAttributesに合わせます。最低必要なattributeは次の4つです。
{ "queue_name": "default", "job_class": String, "job_id": String, "arguments": [ String ] }
key | value |
---|---|
queue_name | 基本defaultで固定。起動時に--queue オプションで変更している場合はそれに合わせる |
job_class | Active Jobのクラス名を指定 |
job_id | 任意の文字列 |
arguments | Jobで処理する文字列。SQSの256KB制限まで詰め込める |
例として{"foo": "bar"}
をActive Jobで処理したい場合のメッセージは次のようになります。
# message.json { "queue_name": "default", "job_class": "AwesomeJob", "job_id": "12345167890", "arguments": [ "{\n \"foo\": \"bar\"\n}" ] }
このJSONをAWS CLIでエンキュー可能です。
aws sqs send-message --queue-url https://sqs.ap-northeast-1.amazonaws.com/123456789012/active-job --message-body file:///message.json
さらに応用としてEventBridgeを利用してSQSにメッセージが送信することで全てのAWSサービスからActive Jobをトリガーすることが可能です。
例えばSESでメールを受信したらS3にメールを保存して、そのイベントをInput Transformerで上記のJSONに整形してSQSに送ることでAction Mailboxのように動かすことができたりします。
SQSの前にEventBridgeを挟むことでイベントのプロデューサーはコンシューマーのことを気にせずただイベントを発行するだけで良くなります。そうすることで各コンポーネントを疎結合にしてRailsのモノリシックアークテクチャの恩恵を受けながらイベント駆動パターンを実装しやすくなります。
まとめ
SQSでActive Jobを利用する方法について紹介しました。 LCLでは本番環境で直列で実行した場合に10時間を超えるバッチジョブを分割して非同期かつ並列に実行することで、毎日大量のデータを扱うことができています。
今後はActive Jobに移行できていない大型のバッチジョブの移行を目標にしています。
採用情報
LCLでは開発メンバーを募集しております!
もし興味をお持ちになりましたらお気軽に応募してください。
*1:ElastiCacheやMemoryDB for Redisのマネージドサービスもありますがインスタンスの管理は依然として必要になるので気軽には手を出しにくいと感じています。