LIFULL で売却査定サイトの開発をしている、ジョン ヨンソクです。
この記事では、15 年間稼働しているメール配信バッチから非同期メール配信システムへのリプレイスをどのように行ったかについての共有をします。
また記事の最後では、この開発に挑むときの自分の考え方、感想なども記しました。
- リプレイス背景
- 使用技術
- 設計図
- 処理の流れ
- 結果
- 挑戦
- 開発に臨む時の考え方、意識したこと
- まとめ
- PR
リプレイス背景
既存では不動産匿名査定*1依頼がユーザーからあった際、その通知を査定依頼受付している各不動産会社に通知している仕組みが、1 時間に 1 回起動するバッチアプリケーションとして稼働していました。
不動産匿名査定の通知メール配信バッチは今から 15 年ほど前に作られたものです。 そこからほぼ手を入れられずに運用していたため、 インフラメンテナンスの度に再実行を手動で行う必要があり、運用負荷の高いものとなっていました。
運用コスト削減や、不動産会社が依頼後即配信となり対応しやすくなるメリットを見込んで、 1 時間に 1 回送信だったものを依頼後即送信するようにリプレイスをしました。
使用技術
Email 送信サービス: Customers Mail Cloud
言語: JavaScript
AWS アーキテクチャ: SNS, SQS, Lambda, CloudWatch
Deploy Tool: Serverless Framework
Test: jest
静的コード分析: ESLint
ci/cd: GitHub actions
Serverless Framework
Serverless Framework(以下 sls)は、Node.js を使用して記述された無料のオープンソース Web フレームワークです。
sls でコード化すると AWS アーキテクチャを AWS 管理画面から設定をする必要が無くなるので、テストと本番環境で設定が異なる状態になりにくいメリットがあります。
インフラ開発と運用コスト面でもメリットがあって、すべての AWS アーキテクチャは sls で開発を行いました。
設計図
処理の流れ
匿名査定完了 → AWS SNS → AWS SQS → AWS Lambda(メール配信)
一般的な Amazon SNS シナリオで実装しました。
匿名査定が完了した場合、ユーザーはメール送信処理を待たずに完了画面へ遷移されます。 裏側では、非同期的にメール送信処理が lambda で行われる仕組みにしました。
同一メールの複数回配信の防止
今回は、複数回同一のメールが配信されないようにする要件がありました。重複不可の場合は SQS の標準形式よりも FIFO の方が適切でした。
SQS を FIFO で使用する場合は SNS も FIFO である必要があるため、SNS、SQS 両方 FIFO で作成しました。
うまく重複除外が行われるためには、ユニークな値を重複除外 ID に指定する必要があります。 今回は査定情報にあるユニークな ID を重複除外 ID に指定して、重複送信が防止できるようにしました。
匿名査定完了したら、Amazon SNS トピックにメッセージを発行
ユーザーが不動産売却匿名査定サイトで査定依頼を完了した時の SNS に渡す各メッセージは、最大 256 KB のデータを含めることができるため、社内 API サーバへのリクエストに必要な最低限の情報のみ渡すようにしました。
SNS 経由で SQS にキューを投入
SNS 経由で SQS にキューを投入するためには SNS トピックへサブスクライブをする必要があります。 以下のように SQS の ARN を設定すると、サブスクライブを設定できます。
# SNS resources: Resources: Topic: Type: AWS::SNS::Topic Properties: FifoTopic: true Subscription: - Endpoint: ${ SQSのARN } Protocol: sqs TopicName: ${ TopicName }
SQS をトリガで Lambda を実行
Lambda は SQS をトリガするようにします。 SQS をトリガにするとメッセージが入り次第、Lambda が実行され、メールで通知ができます。 sls で Lambda のトリガを設定する場合は SQS の arn を指定する以外も複数の方法がありますので、詳細は docs をご参考ください。
functions: sendAnonymousOrderMail: # SQSをトリガにする events: - sqs: arn:aws:sqs:region:XXXXXX:MyFirstQueue
SQS のメッセージの情報で、社内 API サーバからメール本文に必要な情報を抽出
メール送信に必要な情報を SQS から得た情報を利用して、社内 API サーバから必要な情報を抽出する必要がありました。 社内の API サーバと疎通ができるように VPC、subnet の設定を行いました。 以下は sls 上で VPC、subnet の設定例を簡単に記載しました。 詳細は docs をご参考ください。
functions: sendAnonymousOrderMail: handler: functions/sendAnonymousOrderMail.handler vpc: subnetIds: [ subnetIdをArray形式で入れる ] securityGroupIds: [ securityGroupIdをArray形式で入れる ] events: ...
メール送信サービス Customers Mail Cloud(CMC)
差込み文字を使用して、宛先ごとにパーソナライズされたメールを生成し、一括送信するしくみを提供する、Emails/bulk を使用しました。
パラメータ to に指定できる最大メールアドレス数は 1000 となっているので、一回のリクエストでメールを 1000 回分送信できます。 そのため、email 数を 1000 を基準で loop をかけてリクエストをするように実装しました。
査定可能会社ごとにメールの本文を作成する
各不動産ごとにパーソナライズされたメール文面を作成して送信する処理は CMC の差込み文字を使用しました。
CMC の差込み文字は Lambda 内で実装するより工数が減り、CMC 側でロジックの処理するためリソース的にもメリットがあります。
パラメータ to の使用例
[ { "name" : "たろ不動産会社", "address" : "user1@example.com", "memberId" : "00000001" }, { "name" : "まろ不動産会社", "address" : "user2@example.com", "memberId" : "00000002" } ]
メール文面の使用例
((#name#))様( ID ((#memberId#)) ) いつもご利用ありがとうございます。 ...
メール送信失敗
設計図の 5~9 番の内容です。 SQS キューのメッセージを 3 回数正しく処理できなかった場合、以下のように設定をしてデッドレターキュー(以下 DLQ)へ移動されるようにしました。
# SQS resources: Resources: SQSQueue: Type: AWS::SQS::Queue DependsOn: - DeadLetterQueue Properties: ... RedrivePolicy: deadLetterTargetArn: ${ DLQのarn } maxReceiveCount: 3
DLQ にメッセージが入ったことを通知用の SNS トピックにメッセージを発行するため、 マトリックス生成時に ApproximateNumberOfMessagesVisible を選択し、閾値は 1 にします。 指定した閾値を超えると CloudWatch Alarm にて SNS に publish されます。
通知用の SNS をトリガで実行される Lambda では、DLQ の障害情報メッセージを slack へ通知します。
次は、どんな場合メール送信が失敗するかについて洗い出してみました。
cmc にリクエストするときのパラメータが不正の場合
メール送信に必要な情報として空のような不正のパラメータが渡されて Lambda の処理が失敗した場合、3 回まで Lambda は再実行されるようにしました。3 回数再実行したうえ、正しく処理ができなかった場合、SQS のメッセージが DLQ に入ります。 パラメータエラー
DLQ に入ったメッセージに対して、メール再送自動処理が必要な場合は DLQ をトリガにした失敗メール再送用の Lambda を実装することも可能です。しかし、追加実装せずとも、失敗時の再実行数やタイムアウトを適宜に変更することで、一時的なエラーは防げられると思います。
cmc 側での配信失敗
lambda では正常に処理が完了されたとしても、CMC 側で SMTP 通信に失敗や宛先サーバから一時的なエラーにより送信できない場合があります。 基本的に CMC では失敗時の再送機能を提供しているため、一時的なエラーなどはちゃんと再送されるようで、今現在は問題なく送信されています。
結果
メール配信システムのリプレイスをしたことで、 インフラメンテナンスの度に再実行を手動で行う必要がなくなって、運用負荷が大きく削減されました。
また、不動産会社の体験としても、これまでは 1 時間に一度のメール配信だったものが、依頼後即配信となり対応がしやすくなりました。
挑戦
今まではリーダーのサポートを受けて仕様に基づいたコーディング業務をメインにしてきました。
一方、今回の開発に必要な aws と serverless framework の経験もありませんでしたし、具体的な設計した経験も独力でプロジェクトを推進していく経験もありませんでした。
また、これまでやってきたプロジェクトは 1 ヵ月以内で完了する小さなものがほとんどでしたが、今回のリプレイスは 4 ヵ月程度の比較的大きなプロジェクトでした。
今回の案件は開発経験、規模的に私にとって新しい領域への良い挑戦となりました。
開発に臨む時の考え方、意識したこと
開発難易度がこれまでに比べて上がったので、スムーズに進めるために、設計に必要な AWS アーキテクチャの知識を得られる SAA 試験の勉強をしました。 結果、試験には合格し、実際にプロジェクト途中で大きく設計が変わることもなく、順調に開発する助けになりました。
また、今回はアーキテクチャや api を選定して推進する必要があったので、非機能要件として最も意識した部分はリソース的な側面です。
たとえば、既存の処理ではバッチアプリケーションでリソースを使用してメール文面文字置換ロジックを処理していました。 既存の処理をそのまま従うのではなく、Lambda のリソースを最小限で使用するため、CMC のリソースを活用する文字置換方式に変えました。
さらに、既存の要件どおりならメール送信時に cc の設定をする必要があるため、リクエスト 1 回に 1 回送信する api を使用しなければなりませんでした。 今回の案件は大量にメールを送信するので、リクエスト回数と比例してリソース消費も増えると考えました。 問題のない範囲で既存の仕様を一部変更することにより、大量送信に向いている api に変更できました。
まとめ
本記事では 15 年間稼働しているメール配信バッチから非同期メール配信システムへのリプレイスを、どのように実装したのかについて共有しました。
LIFULL では、やってみたいと言う人の支援や背中を押してくれる風土があって、このようなアプリケーション構築において基本設計から担当し、最後まですべての改修案件を独自に推進する挑戦ができました。
加えて、周りにはプロフェッショナルな人が多く多方面で意見を求めることができて、コミュニケーションを取りやすい環境のおかげで、ただ挑戦で終わるのではなくしっかり成果を出して成長につながる経験ができました。
最後に、LIFULL では一緒に働く仲間を募集しています。この記事を読んで LIFULL に興味ができた方は求人情報も御覧ください。
PR
不動産売却の時も LIFULL HOME'S へ!
*1:不動産匿名査定:個人情報いらずで匿名で不動産の査定ができるサービス