マルチテナントを支えるkintoneのメール送信戦略

こんにちは!kintoneチーム DevOpsエンジニアの @szkayeah です。

Webサービスを使っていると、何らかの操作をトリガーにメールが届く時がありますよね。サイボウズの kintone(キントーン) も例外ではありません。「データを登録したよ」とか「Aさんからコメントがあったよ」など主に通知のために普段から多数のメールが送られています。

kintone は、エンドユーザーが業務アプリを自由に作成できるマルチテナント型のクラウドサービスです。ゆえに、テナントによってご利用方法が多種多様に渡ります。メールでの通知に関してもエンドユーザーが柔軟に設定できるため、テナントごとにメール送信量に差があるといった現状があります。

本日は、私のチームが運用している AWS版kintone でメールを安定して送るためにどんな仕組みを作っていったかを紹介します!

メールは非同期で順次送信されていく

非同期ジョブとしてメールは順次送信されていく

kintone で扱うメールは、非同期ジョブとして一旦 queue に積まれ、裏側で順次送信されていきます。

メール送信ジョブかそれ以外のジョブかに関わらず、非同期ジョブはまず同じ queue に enqueue されます。 非同期ジョブ用 queue はテナントごとに分かれており、複数ある queue からフェアネスを考慮しつつ次に dequeue するジョブを判断していきます。 dequeue されたジョブは、その種類により適切な worker に処理が依頼されるという仕組みになっています。

以降、下記の呼び方を前提に記載します。

  • dispatcher(ディスパッチャー): テナントごとに存在する非同期ジョブ用 queue から次に処理するジョブを割り当てるモジュール。
  • worker(ワーカー): 受け取った非同期ジョブを実際に処理して結果を返すモジュール。

Amazon SES の制限

AWS版kintone では メール送信のための worker で Amazon SES (Amazon Simple Email Service) を利用しています。 Amazon SES では以下の2つの送信クォータがあります(2021年7月現在)

  • 24時間に送信できるメールの最大数
  • 1秒間に受け入れることができるメールの最大数

クォータは、日々少しずつメール送信量が増えている場合は自動で引き上げられますが、 意図せず急激にクォータを超えるようなメール送信が発生した場合、Amazon SES側でメール送信がブロックされる場合があります。

従来の kintone のメール送信ではジョブがあればどんどん Amazon SES にリクエストしており、クォータに達する可能性がありました。 安定した運用のためには、急増したメール送信は適度にスロットリングしつつ、いつも通りの送信量でご利用のテナントではいつも通りにメール送信が使える仕組みが必要でした。

AWS版kintoneで立てた戦略

Amazon SES へのリクエストを制御するために、AWS版kintoneで2つの制限を入れる事にしました。

  • A: 全テナントで単位時間あたりに送信できるメール数
  • B: 1テナントで単位時間あたりに送信できるメール数

A の閾値を超えた場合には全テナントでスロットリングを、B の閾値を超えた場合にはそのテナントのみスロットリングします。 AWS版kintoneの運用環境では一部のテナントが大量にメールを送信するケースが多く、大抵は B の制限によって他のテナントへの影響を防ぐことができます。 以下、制限 B についてメール送信のスロットリング戦略をご紹介します。

戦略1: ジョブの処理を sleep させてスロットリング

ジョブの処理をsleepさせてスロットリング

一番単純な方法は、メール送信ジョブを worker で処理する際に、閾値超えのテナントからだった場合は sleep させることでした。

非同期ジョブは並列で実行されるのですが、AWS版kintone全体で同時実行数に上限を設けています。 これは、意図せず許容量を超えるジョブが発生してしまった際も安定運用を持続するためです。

worker で sleep させてスロットリングする実装はシンプルなのですが、待っている間は処理中と同等のため同時実行数を消費します。 同時実行数の上限に達している間は次のジョブは割り当てられないため、もし sleep するメール送信ジョブが沢山発生してしまうと、他の種類の非同期ジョブにも影響が及ぶ懸念がありました。

戦略2: ジョブを積み直してスロットリング

ジョブを積み直してスロットリング

もうひとつには、メール送信ジョブを worker で処理する際に、閾値超えのテナントからの場合は処理を中断しジョブを queue に積み直すことでした。 queue のジョブは順次割り当てられるので、一定時間経った後にリトライされる形でスロットリングを実現する戦略です。

ジョブを queue に積み直す戦略では、戦略1でご説明した非同期ジョブの同時実行数を長時間消費することはないのですが、短時間で無駄なリトライを繰り返す懸念がありました。 queue から次に処理するジョブはごく短い周期で割り当てられるので、例えばスロットリング中のテナントからメール送信ジョブが大量に積まれている場合、制限が解除されるまで dequeue して積み直しを繰り返すことになります。 ジョブが割り当てられてから中断されるまでは少なからず同時実行数を消費するので、戦略1と同様にメール送信以外の非同期ジョブへの影響の心配がありました。

戦略3: メール送信専用 queue を持ちテナント単位でスロットリング

戦略1・2ともに、AWS版kintone全体で設定している非同期ジョブの同時実行数の消費による問題がありました。 上限に達するとメール送信以外の非同期ジョブのスケジューリングにも影響が及ぶため、影響範囲を絞りたい思いがありました。

最終的に私たちはメール送信 worker 側にもメール送信専用の queue を持つことを選択しました。 実装は戦略1・2よりも複雑になるのですが、影響範囲をメール送信だけに絞るという要件にマッチしていました。

メール送信専用queueを持ちテナント単位でスロットリング

メール送信 worker のメール送信専用 queue にジョブが積まれた後は、worker の中だけで完結し順次メール送信をするため、AWS版kintone全体で設定している非同期ジョブの同時実行数にはカウントしません。 何らかの理由でメール送信に失敗し、ジョブをリトライする場合も worker 内のメール送信専用 queue への積み直しになるため、他の種類の非同期ジョブへの影響はありません。

Amazon SQSでメール送信専用queueを実装

メール送信専用 queue には Amazon SQS (Amazon Simple Queue Service) のFIFOキューを利用しています。 FIFOキューは厳密な順序でメッセージを保持します。 メール送信専用 queue では、1つのメール送信ジョブを Amazon SQS の1メッセージとして扱っています。

また、Amazon SQS には複数のメッセージをグルーピングするためのメッセージグループという機能があります。 個々のメッセージはメッセージグループIDを持つことができ、同じメッセージグループIDを持つメッセージは同一のグループとみなされます。 メール送信専用 queue のメッセージでは、メッセージグループIDにテナントごとに振られているID(以下テナントID)を設定しています。 つまり、テナントごとにメッセージをグルーピングしています。

更に、Amazon SQS では Visibility Timeout(可視性タイムアウト) という概念があります。 これは、メッセージの二重受信を防止するために一度受信したメッセージを一定期間受信できなくする仕組みです。 この「一定期間受信できなくする」という特徴を利用して、メール送信のスロットリングのために賢くリトライする仕組みを考えました。

あるメッセージが受信され Visibility Timeout の期間に入ると、そのメッセージに紐づく同一メッセージグループの全てのメッセージが受信できなくなります。 AWS版kintoneでは、まずメール送信専用 queue からメッセージを受信し、メッセージグループID(=テナントID) より単位時間あたりに送信できるメール数を超えていないかチェックします。

閾値を超えていた場合、その受信したメッセージに Visibility Timeout を十分な時間に設定します。Visibility Timeout の期間は同一メッセージグループ、つまり特定テナントの全てのメッセージは受信できないので、メール送信がストップされます。 設定した Visibility Timeout 期間中でも別のメッセージグループのメッセージは受信できるため、制限中以外のテナントは影響を受けずにメール送信されます。

Visibility Timeout の期間を終えると再び制限中だったテナントのメッセージも受信できるようになるため、再処理のタイミングで閾値を超えていなければ無事メール送信されます。

さいごに

  • まとめ
    • パブリッククラウドの制限に抵触しないよう、メール送信に制限を入れることにした。
    • メール送信の制限にあたって、テナント間のフェアネスや他機能への影響を踏まえて戦略を決定した。

特定のユーザーの操作によって、影響範囲が全体に及ぶようでは安定した運用は実現できません。 どんなWebサービスでもサービス自身を守るためにある一定の制限が設けられています。 今回はAWS版kintoneについて、利用しているAWSサービスの制限値をクリアしつつ、kintone自身がメール送信を安定して送るための仕組み紹介しました。

私のチームでは引き続きAWS版kintoneの安定運用に努めてまいります。 活動に興味のある方はぜひ DevOpsエンジニア(AWS版kintone) のキャリア採用募集もご覧ください。

ありがとうございました!