SMS配信の冗長化と配信プロバイダの自動切り替え

エンジニアの佐野です。カンムでは SMS を多用しています。例えば以下のようなポイントで使っています。

  • ユーザ新規登録時の認証コード送信
  • ログイン時の認証コード送信
  • 3Dセキュア 認証時
  • 後払い機能利用時
  • ユーザにメッセージを送る時
  • ...etc

ここでもし SMS 送信ができないような事案が発生するとユーザの体験が悪くなり KPI にかなりのダメージが出ます。今回は SMS 送信が滞らないように戦った歴史と設計について書きます。

  1. SMS 配信サービスの利用と初期の頃の設計
  2. SMS 配信時に問題が発生するポイント
  3. PaaSoo 導入と手動切り替え時代
  4. Nexmo *1 接続時のエラーハンドリングと切り替え自動化
  5. Nexmo からユーザに SMS が届くまでの正常性確認と切り替えの自動化
  6. まとめ

1. SMS 配信サービスの利用と初期の頃の設計

カンムでは SMS 配信サービスとして NexmoPaaSoo を併用しています。Nexmo がプライマリ、PaaSoo がセカンダリという扱いです。 Nexmo の調子が悪いときは PaaSoo に切り替えます。

しかし元々は Nexmo だけを使っていて例えば認証コード送信およびその検証は次のような実装となっていました。

1.1 DB 設計

送信した認証コードの管理については次のように行っています。

  • phone_number_verification_request: 新規登録/ログイン...などを行うときに INSERT するテーブル。実際には有効期限であったり他のカラムがあるがここでは省略する。
  • phone_number_verification_code: こちらも新規登録/ログイン...などを行うときに INSERT するテーブル。phone_number_verification_request と 1:1 で紐付いているが紆余曲折あって別テーブルとなっていて認証コードはこちらに保存してある。ユーザが SMS を受信して受け取った認証コードを入力した際にここで保存されているものと一致しているかどうかの検証を行う。
  • phone_number_verification: 検証が完了したら INSERT するテーブル。

1.2 SMS による認証コード送信

ユーザ新規登録時やログイン時などに次の様なシーケンスでユーザに SMS を送信して認証コードを届けます。

1.3 認証コードの確認

ユーザの端末に SMS が届くとユーザは認証コードを入力、サーバ側では phone_number_verification_code を検索し、マッチしていれば phone_number_verification を作って認証完了とします。他にも端末のチェックであったり不正な認証が発生しないような処理は入っているのですが割愛します。

2. SMS 配信時に問題が発生するポイント

図示すると以下の箇所になります。

1つめは Nexmo に接続するときです。ここで Nexmo に接続できない、もしくは HTTP 5xx が返る可能性があります。このケースのときは DB はロールバックされ、ユーザにはエラー応答が返ります。 2つめは Nexmo に SMS 送信要求を出したあとになります。このケースのときは Vandle API から見ると SMS 送信は正常系で終了します。しかし Nexmo から先のキャリアネットワークであったりユーザの端末で何かが起きているとユーザは送信された SMS を受け取ることができません。ユーザはひたすら SMS の到達を待つことになります。

3. PaaSoo 導入と手動切り替え時代

データを見てみるとどうやら2019年のケツから2020年頭あたりに PaaSoo を導入したようです。たしかこのときにユーザが SMS を受け取ることができないという事案が長時間発生し、何人かでオフィスにこもって状況を見守っていました (見守ることくらいしかできませんでした)。

PaaSoo 導入時、単純にセカンダリを用意した、というだけではなく、phone_number_verification_provider_ratio というテーブルを用意してそこで Nexmo と PaaSoo の利用比率を管理、その比率に応じて送信先を振り分けるという戦略になりました。関連を作らない独立した単純なテーブルです。

Nexmo の不調を検知した時(接続時エラー、ユーザからのクレーム、etc)は担当者が管理画面から phone_number_verification_provider_ratio の設定をいじるという手運用でした。過去のデータを見てみると 2022年10月20は早朝から頑張っている形跡が見られました。

Nexmo が調子悪いならしばらく PaaSoo に切り替えたままにしときゃいいじゃねぇか、と思うかもしれませんが、 PaaSoo はプリペイド方式で契約をしており、利用可能残高が枯渇したらそれはそれで渋い話になるということもあり、しばらく使ったら Nexmo に切り戻す、というオペをしていました。今でもカンム社内では PaaSoo は一時利用という位置づけです。 この phone_number_verification_provider_ratio を利用して SMS 配信時のロジックにパッチを当てました。

4. Nexmo 接続時のエラーハンドリングと切り替え自動化

手オペ時代の次は自動化です。「SMS 配信時に問題が発生するポイント」に書いた1つめのポイント、まずは Nexmo に接続自体ができない、エラーが返ってきたケースに対応を行いました。Git を見るとこれは 2023年5月に行われました。Nexmo に接続して問題が発生したら即座に PaaSoo に切り替えるようになりました。PaaSoo 側でも問題が起きたらそれは諦めます。

5. Nexmo からユーザに SMS が届くまでの正常性確認と切り替えの自動化

そして最近行ったのがこちらです。「SMS 配信時に問題が発生するポイント」に書いた2つめのポイント、Vandle から Nexmo への SMS 送信要求は成功したが、ユーザに SMS が届いていないであろうときに PaaSoo に自動切り替えする対応です。

ここで「ユーザにとどかないとは?」ですが、DB 設計の箇所で、SMS 送信時に phone_number_verification_request が作られ、ユーザが認証コードを入力することで phone_number_verification が作られることを述べました。phone_number_verification_request が作られてから長い時間 phone_number_verification が作られていない場合は、「実は SMS がユーザに届いていないのではないか?」とします。

これを SMS 到達率として扱い、次のような SQL で3分間程度の到達率を計算します。なおしばらくリクエスト自体がない場合は1.0としています。

SELECT
  CASE WHEN COUNT(pnvr.id) > 0 THEN
    COUNT(pnv.request_id) / COUNT(pnvr.id)::numeric
  ELSE 1.0
  END AS success_rate
FROM phone_number_verification_request pnvr
LEFT JOIN phone_number_verification pnv ON pnvr.id = pnv.request_id
WHERE pnvr.requested_at > now() - interval '3 minutes'

これを用いて次のようなロジックに変更しました。phone_number_verification_provider_ratio で Nexmo:PaaSoo = 100:0 になっているときに SMS 到達率が低下していたら、phone_number_verification_provider_ratio を Nexmo:PaaSoo = 0:100 に変更します。そしてそれと同時にジョブキューを投入し、しばらくしたら Nexmo に戻るようにしました。

まだ改善点はあると思いますが、長い時間をかけて冗長化および切り替えの自動化が完了しました。

6. まとめ

  • 以下のようなタイムラインで SMS 配信プロバイダの利用と冗長化を行った
    • 2016年〜2019年: Nexmo 1つで運用
    • 2020年1月: PaaSoo 導入と SMS 配信プロバイダの手動切り替えが可能になった
    • 2023年5月: Nexmo 接続エラー時の自動切り替え対応
    • 2024年11月: SMS 未到達時の自動切り替え対応 (完全体?)

セカンダリとして PaaSoo を導入した契機となるような大規模障害が発生しないとこのようなものは後回しにされがちです。Nexmo ダウン時であったり SMS 未到達時にどんくらいの損失出ているのかを計算して改善の優先度を上げてもっと早めに着手すればよかったな、と今更ながらに思ったりしました(計算の結果たいしたことない、という結論になる可能性もあるが...)。

とりあえずこれにてオンコールの手間は減ったはずです。

おわり

*1:現在は Vonage という名称になっているようです