Webuilder240

ActionMailerでAWS SQSにキューイングする

2023-05-17 20:21:04 +0900

Lambda SQS ActionMailer Ruby Ruby on Rails

なぜSQSにキューイングしたいのか

これまで素朴にActionMailerでSidekiqでメールを送信していたが、スケールアウトできるメール送信基盤を作るためです。
具体的には送信処理をSidekiqやResqueはなく、SQS + Lambdaでメール送信するマイクロサービスを作成し、マイクロサービス上でメールを送信するようにしたかったためです。

スケーラビリティの確保
運用コストは上がるかもしれないですが、キューイングさえできてしまえば、SQSとLambdaの組み合わせだとRailsよりはコストかけないでスケーラビリティの確保ができるのではないかと考えているためです。

この記事でも説明しているように、モノリスと小さいサーバーレス(マイクロサービス)を組み合わせて運用するのが一番幸せになれる構成だと思うので、こちらの記事についてもご覧になってください。



コストパフォーマンスに優れる
たくさんメールを送信するプロダクトでもないので、Sidekiqワーカーやなにかワーカープロセスを起動する仕組みに比べるとプロセスやコンテナを常に起動管理しておく必要がないので、管理・運用コストも下がるのではないかと見込んでいます。

スレッドセーフについて
Sidekiqでよくありがちなスレッドセーフ出ないことによって不具合なんかも稀にあるので、
メール送信マイクロサービスの実装次第ですが、あまり意識しなくともよくなるのもモチベーションの一つです。

ActionMailerのインターフェイスでキューイングできると書き換えコストが下がる
これまでAcitonMailerで送信されていたRailsアプリケーションであれば、ActionMailerのインターフェイスでキューイングができると、実装者は意識しなくてもメール送信にマイクロサービスを利用することがで切るようになるためです。

ActionMailerのdelivery_methodを変更するためのクラスを実装する

ActionMailerのdelivery_methodを変更することで対応が可能です。
deliver! メソッドに実際にメールを送信する処理を送信する処理を行います。
Mailerからdeliver_laterであっても、deliver_nowを呼び出す場合でも、必ずAWS SQSへのキューイングを行うようにします。
require 'aws-sdk-sqs'
require 'aws-sdk-sts'

class Mail::Sqs

  def initialize(settings)
    @settings = settings
  end

  def deliver!(mail)
    entries = [
      {
        id: SecureRandom.uuid,
        message_group_id: SecureRandom.uuid,
        message_body: {
          from: mail.from.first,
          to: mail.to,
          subject: mail.subject,
          body: mail.body.raw_source
        }.to_json
      }
    ]
  
    queuing(entries)
  end

  private

  def queue_name
    ENV['SQS_QUEUE_NAME']
  end

  def queue_url
    ENV['SQS_QUEUE_URL']
  end

  def queuing(entries)
    sqs_client = Aws::SQS::Client.new(region: ENV['SQS_REGION'])
    sqs_client.send_message_batch(
      queue_url: queue_url,
      entries: entries
    )
    true
  rescue StandardError => e
    puts "Error sending messages: #{e.message}"
    false
  end
end

ActionMailerのdelivery_methodを設定する

initializers/sqs.rb

というファイルを作って、下記コードをコピペしましょう。
require './lib/mail/sqs.rb'
Rails.configuration.to_prepare do
  ActionMailer::Base.add_delivery_method :sqs, Mail::Sqs, api_key: ENV['SENDGRID_API_KEY']
end
これで、ActionMailerからdeliver_later, deliver_nowを呼び出すことでSQSにメールをキューイングを行って成功したタイミングで処理完了として扱っています。

これでキューイングしたSQSをLambdaで送信する仕組みを作れば完了です。
送信側については気が向いたら書きます。

メモ

キューイングするJSONの内容を今後開発するマイクロサービスに合わせたいのと、
依存するライブラリを増やしたくないためAWS SQS SDK以外のライブラリを利用しないようにしました。

関連しそうなブログ