なぜSQSにキューイングしたいのか
これまで素朴にActionMailerでSidekiqでメールを送信していたが、スケールアウトできるメール送信基盤を作るためです。
具体的には送信処理をSidekiqやResqueはなく、SQS + Lambdaでメール送信するマイクロサービスを作成し、マイクロサービス上でメールを送信するようにしたかったためです。
スケーラビリティの確保
運用コストは上がるかもしれないですが、キューイングさえできてしまえば、SQSとLambdaの組み合わせだとRailsよりはコストかけないでスケーラビリティの確保ができるのではないかと考えているためです。
この記事でも説明しているように、モノリスと小さいサーバーレス(マイクロサービス)を組み合わせて運用するのが一番幸せになれる構成だと思うので、こちらの記事についてもご覧になってください。
コストパフォーマンスに優れる
たくさんメールを送信するプロダクトでもないので、Sidekiqワーカーやなにかワーカープロセスを起動する仕組みに比べるとプロセスやコンテナを常に起動管理しておく必要がないので、管理・運用コストも下がるのではないかと見込んでいます。
スレッドセーフについて
Sidekiqでよくありがちなスレッドセーフ出ないことによって不具合なんかも稀にあるので、
メール送信マイクロサービスの実装次第ですが、あまり意識しなくともよくなるのもモチベーションの一つです。
ActionMailerのインターフェイスでキューイングできると書き換えコストが下がる
これまでAcitonMailerで送信されていたRailsアプリケーションであれば、ActionMailerのインターフェイスでキューイングができると、実装者は意識しなくてもメール送信にマイクロサービスを利用することがで切るようになるためです。
具体的には送信処理を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へのキューイングを行うようにします。
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
endActionMailerの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で送信する仕組みを作れば完了です。
送信側については気が向いたら書きます。
これでキューイングしたSQSをLambdaで送信する仕組みを作れば完了です。
送信側については気が向いたら書きます。
メモ
キューイングするJSONの内容を今後開発するマイクロサービスに合わせたいのと、
依存するライブラリを増やしたくないためAWS SQS SDK以外のライブラリを利用しないようにしました。
依存するライブラリを増やしたくないためAWS SQS SDK以外のライブラリを利用しないようにしました。