LCL Engineers' Blog

バス比較なび・格安移動・バスとりっぷを運営する LCLの開発者ブログ

aws-sdk-railsでSQSをActive Jobのアダプターとして使う

はじめに

この記事はLCL Advent Calendar 2021 - 4日目です。

qiita.com

バックエンドエンジニアの星野です。このアドベントカレンダー同じ人しかいないって?気のせいです。

LCLのバッチジョブ実行基盤解説記事の最後のエントリになります。 最終日はSQSとActive Jobについてです。この記事ではActive Jobそのものについては解説しませんのでRailsガイドを適宜参照してください。

railsguides.jp

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のコードベースが小さく処理が追いやすいため扱いやすいと感じています。

github.com

設定方法

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_secondsreceive_wait_time_secondsaws-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)

f:id:hosht:20211203171724p:plain
さまざまな環境で実行されているRailsから呼び出せます

また、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のように動かすことができたりします。

f:id:hosht:20211203173944p:plain
SES → S3 → EventBridge → SQS → ECS(Active Job) → Aurora

SQSの前にEventBridgeを挟むことでイベントのプロデューサーはコンシューマーのことを気にせずただイベントを発行するだけで良くなります。そうすることで各コンポーネントを疎結合にしてRailsのモノリシックアークテクチャの恩恵を受けながらイベント駆動パターンを実装しやすくなります。

まとめ

SQSでActive Jobを利用する方法について紹介しました。 LCLでは本番環境で直列で実行した場合に10時間を超えるバッチジョブを分割して非同期かつ並列に実行することで、毎日大量のデータを扱うことができています。

今後はActive Jobに移行できていない大型のバッチジョブの移行を目標にしています。

採用情報

LCLでは開発メンバーを募集しております!

もし興味をお持ちになりましたらお気軽に応募してください。

www.lclco.com

*1:ElastiCacheやMemoryDB for Redisのマネージドサービスもありますがインスタンスの管理は依然として必要になるので気軽には手を出しにくいと感じています。