問題点
メール配信設定に関する機能についてです。
SendGrid では、メール配信を行うために (CNAME・ないし MX/TXT 形式の)DNS 登録・及び認証が必須となっており、その認証状態を確認するための Domain Validation API が提供されています。
その DNS 認証を行うための処理をクラス化していたのですが、無関係の様々な処理(例えば「メール配信結果にまつわる情報取得など」)も役割を持たせられており、責務が肥大化し、いわゆる「大きな泥団子」と化していました。
その状態で過剰にリクエストされた結果、ある時 SendGrid API から Rate Limit 超過エラーを受け取るケースが発生しました。
分析
まず掲題の外部 API が許容している Rate Limit の詳細を確認しました。
公式ドキュメントを見ても詳細は書いていなかったため、実際に呼び出している箇所にログを仕込み、「何分・何秒間に何回までリクエストが許容されるか」を確認しました。
そして、それに耐え得る構成とするため、以下の方針を取りました。
解決方針1:責務分離
まず責務を分離するところから始めました。
「DNS 認証」を行う処理にそれ以上の責務を持たせるべきではないと考えました。
特に自社ではなく、外部連携先の API を呼び出している箇所なので慎重にならないといけません。
結果として、認証処理をクラス化し、利用が必須な箇所のみで呼び出すように API を新たに用意しました。
下記のようなイメージです。
class VerifyEmail
def perform(email)
res = SendgridClient.new.domains.validate(email)
return true if res&.dig(:valid)
return false
end
end
解決方針2:キャッシュ化対応
REDIS を用いて認証情報のキャッシュ化を行いました。
いくら責務を分離したとて、過剰にリクエストされると同じ問題が起きてしまうからです。
なおキャッシュが有効化されている内は情報が更新されないので、その旨ユーザーへの注意喚起も忘れてはなりません。
REDIS にそれ用のキャッシュデータを持たせるため、DB ナンバーを指定した上で追加・取得・削除などの処理を適宜行わせるように修正しました。
下記のようなイメージです。
REDIS_CACHED_VERIFICATION_EMAIL_STATUSES = Redis.new(
host: 127.0.0.1
port: 6379
db: 1
)
redis = REDIS_CACHED_VERIFICATION_EMAIL_STATUSES
# キャッシュの追加
redis.set(email, { status: is_verified }.to_json )
# 有効期限の設定(単位:秒)
redis.expire(email, 60)
# キャッシュの取得
res = redis.get(email)
# キャッシュの削除
redis.del(email)
まとめ
今回の件で、改めて外部 API にリクエストしている箇所は注意が必要だと認識しました。
自社の持ち物ではないため、先方に迷惑をかけないような配慮が必要とされます。
また責務がごちゃ混ぜになった「大きな泥団子」は作業効率を優先して実装が行われると作られがちなので、注意していきたいところですね(自戒の意味も込めて)。