ブロックされなかったプッシュパイプライン

スケジュールバッチジョブとしての通知キャンペーン:対象クエリに押し込まれた資格判定と重複排除、再開チェックポイントを兼ねる送信ログ、プラットフォームクォータへのスロットリング——そして今なら追加する2つのゲート。

通知システムの一番怖い故障モードは障害ではありません。障害なら気づいて、直して、謝れます。私を慎重にさせ続けたのは静かな故障のほうです:少し多めに、少し頻繁に送りすぎると、ユーザーは苦情を申し立てません——「ブロック」をタップし、その人へのチャネル全体が永久に閉じるのです。メッセージングがそのままプロダクトの表面であるチャットプラットフォームのプロダクトにおいて、プッシュパイプラインは実質的に、全顧客との関係への唯一の鍵を握っていました。私たちはその鍵が複製不能であるかのように作りました——そして最後に書くとおり、今なら付ける2つの錠を、当時は付けませんでした。

アーキテクチャ:キャンペーンはスケジュールバッチジョブ

すべてのアウトバウンドキャンペーンはECS上のスケジュールバッチジョブとして、それぞれのリズムで走りました:内見リマインダーは30分ごと、満足度アンケートのプッシュは毎日夕方、新着物件レコメンドのプッシュは週次、もう1種のレコメンドは週2回、そしてそれらに供給する夜間のマスターデータ+スコアリングのパス。アプリケーションコードからのアドホックな送信は存在しません——プロダクトコードは状態を上げ、誰がそれを聞くかはジョブが決めます。

キャンペーン個別ではなく構造的だった部品が2つ:

  • 単一の送信ゲートウェイ。 すべてのジョブからのすべてのプッシュが同じ送信関数を通ります。そこには環境レベルの許可リストゲートも載っていて、非本番環境は物理的に実ユーザーへメッセージを送れませんでした。迂回できる安全ルールはルールではありません。扉が1つであることが、ルールを構造にします。
  • 対象リストであり台帳でもあるデータベース。 各キャンペーンのターゲットはSQLクエリから来て、各送信は通知/ステータステーブルに記録されます。この1つの設計判断が、以下のほとんどを静かに動かしています。

仕事をしたゲートたち

  1. 資格判定は送信時に評価する。 対象クエリはジョブの実行時に走ります——オプトアウト、完了済みの予約、古くなったプロファイルは自然に落ちる。エンキュー時に評価された条件は腐ります。ゲートで評価するとは、もう真実でなくなった事柄について誰かにメッセージしない、ということです。
  2. 重複排除は対象クエリに押し込む。「送信済み」はアプリケーションレベルのチェックではなく、過去送信テーブルへの NOT EXISTS 句でした。このキャンペーンをすでに受け取ったユーザーは、そもそもバッチに入りません。可能な限り安い棄却:重複は1行分の仕事もされる前にフィルタされます。
  3. 再開チェックポイントとしての送信ログ。 ジョブは通知ごとのステータスを記録し、常に「未完了」の行だけを選択します。途中で死んだキャンペーンは、前半の対象者に再送することなく再実行できました——リカバリー手順ではなく、クエリの性質としての再起動安全性です。バッチジョブは失敗します。そうでないと仮定するパイプラインは、最悪の時間帯にあなたを呼び出します。
  4. プラットフォームのクォータに対する、余裕を持ったスロットル。 重量級のキャンペーンは上限付きワーカープールで走り、1000プッシュごとに意図的に一時停止して、メッセージングプラットフォームの公表レート制限の内側に余裕を持って留まりました。制限に体当たりしてバッチの途中でエラーを食べるのではなく。

テンプレートにはこのプラットフォームの他の場所と同じ規律を:サーバーサイドで構成し、コードのようにバージョン管理する——壊れたディープリンクを持つキャンペーンメッセージはパッチできず、悔やむことしかできません。

今なら追加する2つのゲート

正直セクションです。このパイプラインになかった2つの保護と、それぞれの設計上の教訓:

  • 全キャンペーン横断の、ユーザーごとの頻度上限。 各ジョブには常識的なウィンドウと除外条件がありましたが、ユーザーの合計受信箱を代表するものは何もありませんでした:個々には妥当な4つのキャンペーンでも、合計すれば非常識な1週間になりえます。上限はすべてのキャンペーンの上、共有ゲートウェイに属します。まさに、どの単独のキャンペーンオーナーもそれを強制できる立場にないからです。
  • キャンペーン単位のキルスイッチ。 環境の許可リストはすべてを止められましたが、トランザクショナルなメッセージを流し続けながら1つの暴走キャンペーンだけを止めるブレーキはありませんでした。細かいブレーキは使われます。全体ブレーキは、被害が続く間に議論されます。小さな機能ですがインシデント対応での見返りは不釣り合いに大きく、共有送信ゲートウェイはそれがぴったり収まる場所です。

どちらの欠落も実際に私たちを焼きはしませんでした——リズムは保守的で、対象は丁寧に絞られていた——しかし両方とも、「必要だったと証明するインシデント」の前に欲しい類のコントロールです。

メンタルモデル

持ち帰るべき教訓:メッセージングチャネルを、プラットフォームのクォータを通じて支払われる、ユーザーの忍耐という通貨の予算として扱うこと。 クォータはプラットフォームが教えてくれる硬い天井です。ブロックボタンはユーザーが教えてくれる本当の天井で、そちらのほうが低い。上のすべてのゲート——そして欠けていた2つ——は、支出を1つ目ではなく2つ目の天井の下に保つために存在します。

これから作る人へ:送信を一元化してルールを構造にすること。送信ログを背骨にすること(重複排除・再開・集計は同じテーブルでありたがっています)。資格判定は遅く評価し、除外は対象クエリに押し込むこと——そして横断上限とキルスイッチは、安いうちの初日に作ること。パイプラインの仕事は可能な限り多くのメッセージを届けることではありません——来月もまだ歓迎されていることです。