この記事は一休.comアドベントカレンダー2017の5日目です。
宿泊事業部 Platformチーム*1の id:minato128 です。今年一休ではクラウド移行に伴い、メール配信の仕組みを大きく変えました。詳しくは一休✕bitFlyer C#をつかったサービス開発の裏側でお話したスライドがこちらにありますので、興味のある方はご覧ください。
新メール配信基盤への移行 /ikyu-mail-platform // Speaker Deck
さて、宿泊予約やレストラン予約のサービスを提供している一休では、メールをユーザーに届けることはとても大切です。特に予約完了メールが届かなかった場合、(メール以外の確認方法もあるとはいえ)予約が取れたことに気づかず、最悪ユーザーが2重に予約をしてしまう可能性もあります。*2 そこでメールを届けるために、どのようにメール配信基盤のモニタリングや障害が起きたときのリカバリーを行っているかを紹介したいと思います。
前提として
現在このようにメール配信を行っています。 また、日次のトランザクションメールは 13~15万通ほどです。
- 各アプリケーションから Cloud Queue (SQS) に Message (JSON) を Enqueue する
- Worker (Elastic Beanstalk) で Message を処理して SendGrid Web API で送信する
- 成功したら Message を メールログ(DynamoDB) に保存する
- n 回エラーになったら Dead Letter Queue (以下 DLQ とします) に入るように設定
- SendGrid Event Webhook を AWS Lambda で受けてメールログに送信ステータスを反映する
モニタリング
このようなアラートを設定しています。大抵の場合、Datadog のアラートで何かが起きていることがわかり、Logentries のエラーログで原因がわかります。
- Datadog
- Worker の状態異常アラート
- Queue の Message 遅延アラート
- DLQ の存在アラート
- Logentries
- Worker のアプリケーションエラーアラート
障害リカバリー
SendGrid だけでなくアプリのバグや AWS など障害によってメールが送れなかったとき、復旧時にメールを再送してあげる必要があるので、このような仕組みを用意しています。
Worker で Message が処理できず、DLQ に入ってしまったとき
これはそもそも SendGrid まで届いていないケースで、DLQ から送信用の Queue へ Message を移せるようにしています。Message の移動だけなのでわざわざ用意するほどでもないう気もしますが、障害はいつ起こるかわからないし誰が対応するかもわからないので簡単にできるようにしておいたほうがいいでしょう。
メール DLQ 管理画面
特定条件の Message を再送するとき
これは SendGrid まで届いているけれど送れていないかもしれないケース、もしくは単純に再送したいケースで、送信状態や時間帯で DynamoDB からログを抽出して送信用の Queue へ移せるようにしています。
個別再送
- 送信履歴検索から本文を参照し、再送ボタンを押すとそのメールだけ再送できる
- 主にCSチームで使用
一括再送
- ログを検索して件数とプレビューを表示し、再送ボタンを押すと一括再送できる
- 大きめの障害のときに使用
その他
送信ステータスが更新されない送信ログの検知
なぜか送信ステータス更新ができていないことが稀にあるため、AWS Lambda で日次実行してログが存在したら通知するようにしています。(SendGrid が不調のときにも発生しますが、正確な原因はまだわかっていません)
施設からユーザー宛のメールがバウンスしたときに、送信失敗お知らせメールを自動送信
※こちらはモニタリングや障害リカバリーとは関係ありませんが、新しいメール配信基盤の運用開始後にわかったこと*3として書きます。おまけと思ってください。
前提として、施設さまや店舗さま(ホテルやレストランなど。以下施設とします。)からユーザーへメールを送信する機能があります。*4
施設管理のメール送信画面
オンプレ時代は Return-Path が施設のアドレスに設定されていたため、施設側がユーザーへのメールが届かなかったときはバウンスメールが返ってきて気づけたのですが、バウンス管理を SendGrid に委譲したためそれができなくなってしまいました。そこで、
- SendGrid Event Webhook を AWS Lambda で受けてメールログに送信ステータスを反映する
という既存処理のなかに「施設からユーザー宛のメール」かどうかを判定し、送信用の Queue に バウンス通知メッセージを Enqueue するという処理を追加しました。これで送信失敗お知らせメール(バウンスメール相当)を自動送信できるようになりトラブルが減少しました。
明日は @nigauryyy さんの「JSON Facetのススメ」です。