Patreonはいかにファンアウトで通知システムを拡張したか
(patreon.com)- 年間数十億件の通知を処理してきた13年もののシステムは、大規模クリエイター1人あたり数百万件を生成するようになってタイムアウトに直面し、Patreonは受信者別の通知を事前生成するファンアウト層で再設計した
- 受信者リストを小さなバッチに分割し、アプリ内フィード・プッシュ・メールを独立して処理する2段階ファンアウトを適用することで、水平スケーラビリティとチャネル障害の分離を確保した
- 大規模クリエイターのプッシュ・アプリ内フィードは80%、メールは55%高速化し、オーディエンスが4倍に増えた際のエンドツーエンド遅延の増加率も、従来の186%からそれぞれ33%と60%に低下した
- Notification Factoryと単一API
send_fanout_notificationsにより、フィルタリング・資格チェック・ペイロード生成を標準化し、段階別のタイミングと受信者別の結果を追跡して運用とデバッグを簡素化した - 10チーム、30人以上のエンジニアが200件を超える通知を移行し、AIベースのドラフト生成とリーダーシップが設定した明確な期限が、反復作業と長期の並行運用を減らすのに貢献した
大規模クリエイターが露呈させたスケーリングの限界
- Patreonは、クリエイターが投稿を公開したりライブストリームを開始したりする際に、ファンへ年間数十億件の通知を送信している
- 無料メンバーシップのリリースと成長により、クリエイターあたりのファン数が急増し、2025年初頭には大規模クリエイターを中心に深刻な性能ボトルネックが明らかになった
- 従来の投稿通知は、1つの非同期ジョブが全受信者を取得し、ユーザー別設定でフィルタリングし、各受信者のペイロードを生成したうえで、アプリ内フィード・プッシュ・メール配信システムへ渡す構造だった
- 大規模クリエイターの場合、単一ジョブが数百万件の通知生成を担うため継続的にタイムアウトが発生し、大規模なオーディエンスへ通知を安定して届けることが難しくなった
- 従来のフローには2つの構造的な限界があった
- 受信者別の生成コストが1つの非同期ジョブに集中し、水平スケールできず、スループットに明確な上限が生じる
- アプリ内フィード・プッシュ・メールが強く結合しており、1つのチャネルの失敗が他のチャネルまで止めてしまう可能性があった
- 複数のプロダクトチームが高容量通知ごとに別々のスケーリング方式を作ることで短期的な問題は解決したが、プラットフォームの複雑性が高まり、開発・運用・デバッグは難しくなった
- 既存の配信システムは維持しつつ、その前段に共通のファンアウト層を配置し、水平スケーラビリティ、チャネル分離、可観測性を提供することにした
ファンアウトプラットフォームの6つの設計条件
- 新しい層は、大きな受信者リストを小さなバッチへ分割し、受信者別の通知を生成したうえで、既存のアプリ内フィード・プッシュ・メールシステムへ渡すよう設計した
- プラットフォームには次の6つの要件を適用した
- 開発者フレンドリー: 高容量通知ごとにスケーラビリティと信頼性を最初から解決する必要がなく、利用ミスを減らしながら多様なケースをサポートできること
- 水平スケーラビリティ: 受信者リストを自動的に小さなバッチへ分割し、非同期で処理できること
- チャネル分離: アプリ内フィード・プッシュ・メールを独立して実行し、1つのチャネルの障害が他のチャネルへ伝播しないこと
- 優先度処理: 時間に敏感な通知は専用キューとワーカープールを使い、通常通知と分離すること
- 可観測性: フィルタリングおよび配信結果を調査し、通知ライフサイクル全体の遅延時間を測定できること
- 拡張可能性: 受信者リスト生成のような将来機能を、さらなる大規模リファクタリングなしに追加できること
Notification Factoryでビジネスロジックを分離
- プラットフォームのオーケストレーションと通知別のビジネスロジックを分離するため、Notification Factory抽象化を導入した
- 各ファクトリは通知処理の設計図として次の項目を定義する
- 処理優先度
- 適用する通知設定
- 受信者フィルタリング方式
- 通知別の資格チェック
- チャネル別ペイロード生成方式
- このパターンはPatreonの独自分散ジョブエンジンを基盤としており、複数の概念を共有している
- Notificationモデルは、特定の通知に固有で、すべての受信者が共有するデータを含むジョブ引数を定義する
- 紐づくBaseNotificationFactoryサブクラスはジョブハンドラとして動作し、受信者をフィルタリングして通知を生成するための設定とメソッドを含む
- シリアライズされた通知ペイロードだけを受け取るファンアウトジョブが実行すべきビジネスロジックを見つけられるよう、各Notificationモデルを該当するファクトリクラスへマッピングするレジストリを使用する
- 多様な通知タイプをサポートすると同時に、よくある実装ミスや非効率なデータ取得を防ぐ必要があった
- Patreonには複数のユーザーレコード種別とフィルタリング方式があるため、受信者インターフェースは汎用化した
- ユーザー設定と通知別の資格チェックを実装できる明確なフックを提供した
単一APIと2段階ファンアウト
- 以前はチャネルごとに別々の送信APIを使っていたため、アプリ内フィード・プッシュ・メールのフローが互いに異なる可能性があり、プロダクトチームも早すぎる段階からチャネル別の挙動を考慮する必要があった
- 再設計後、エントリポイントを単一APIである**
send_fanout_notifications**に統合した- 呼び出し側は通知ペイロード、受信者リスト、配信遅延の測定に使うタイムスタンプを渡す
- APIは該当通知のファクトリを見つけ、バッチサイズと優先度を決定したうえで、チャネル別ジョブを独立して分散する
- 1つのチャネルの失敗や遅延が他のチャネルを止めることはない
- 第1段階は通知生成のスケールを担当する
- 全受信者リストを小さなバッチへ分割する
- 各バッチは
FanoutNotificationsジョブが処理する - ジョブは受信者をフィルタリングし、通知別の資格チェックを実行したあと、Notification Factoryでチャネル別ペイロードを生成する
- 第2段階は配信引き渡しのスケールを担う
- 生成された通知を、アプリ内フィード・プッシュ・メール専用の配信ジョブへ再びファンアウトする
- 中核となる
FanoutNotificationsジョブハンドラはAPIと既存のチャネル配信システムの間に位置し、1つの巨大なジョブを独立して処理できる多数の小さなジョブへ変換する
受信者単位の観測とデバッグ
- 通知は、ユーザーが設定を無効化している、資格チェックを通過しない、メールアドレスへ配信できないなど、さまざまな理由で失敗またはスキップされることがある
- 従来のプラットフォームでは、特定通知の処理結果を確認するには複数システムのログとデータを組み合わせる必要があり、包括的な受信者別の実行追跡もなかったため、調査に数時間かかることがあった
- 新しいプラットフォームは次の問いに答えられるよう設計した
- 通知送信にどれだけ時間がかかったか
- 最初の受信者リストはどれほど大きかったか
- 何人がどの理由でフィルタリングされたか
- チャネル別に何件が正常に送信されたか
- 特定ユーザーが特定通知を受け取った、または受け取らなかった理由は何か
- プラットフォーム全体で伝搬されるタイミング・ロギングデータモデルを導入した
- 通知フローの各段階でタイムスタンプを記録する
- 集計結果と受信者別結果の両方についてメトリクスとイベントを生成する
- 開発者はロギング属性を付けて、関連エンティティを実行追跡とイベントに含められる
- 可観測性、アラート、ダッシュボード、デバッグツールが改善されたことで、プロダクトエンジニアとサポートチームはプラットフォームエンジニアに依存せず、より多くの問題を調査できるようになった
200件を超える通知のマイグレーション
- Notification Factoryパターンは通知を開発・保守する方法自体を変えたため、単純な**コード変換ツール(codemod)**だけでは移行できなかった
- 各通知のビジネスロジック、データ依存関係、資格ルール、チャネル別の挙動をそのまま維持する必要があった
- 移行ドキュメントと模範的な手作業マイグレーションPRをもとに、AIスキルを構築した
- 開発者は
/migrate-notif-fanout <notif_name>を実行して、一次マイグレーション結果を生成できた - 通知モデルの作成、ファクトリ接続、呼び出し箇所の修正、プラットフォームパターンの適用といった反復作業に効果的だった
- 開発者は
- AIは微妙なビジネスロジックを見落としたり、ハルシネーションを起こしたり、整理が必要な重複コードを作ったりする可能性があるため、エンジニアの判断を置き換えるものではなかった
- 200件を超える通知をマイグレーションし、AI活用を含む大規模な協業により、残りの80%を6週間で完了した
性能改善とプロダクト開発への効果
- 受信者別の通知生成を水平スケール可能なジョブへ移すことで、クリエイター1人あたり数百万件の通知をタイムアウトなしに安定して生成できるようになった
- 大規模クリエイターの高容量通知性能が大きく改善した
- プッシュとアプリ内フィード通知は80%高速化
- メール通知は55%高速化
- オーディエンスが4倍に増えたとき、プッシュ・アプリ内フィードのエンドツーエンド遅延は33%、メールは60%増加
- 同じ条件で従来プラットフォームの遅延増加率は186%だった
- プロダクトチームは高容量通知ごとに別々のスケーリングソリューションを設計する必要がなくなり、開発速度も改善した
- 新しいプラットフォームは2025年のLives、Quips、大規模クリエイター獲得といった主要リリースを支えた
- ファンアウト再設計がなければ、これらのリリースは既存ボトルネックにぶつかり、一回限りのスケーリングソリューションを作るために追加のエンジニアリング時間が必要だった
- 段階別タイミングメトリクスにより、時間が消費される箇所を把握し、速度とコストのバランスに合わせてインフラを調整できるようになった
9か月の並行運用と組織調整
- 稼働中のレガシーシステムを変更しながら複数チームのマイグレーションを調整することが最も難しい部分だった
- 9か月間、2つの通知システムを並行運用した
- すべてのプラットフォーム変更が既存経路と新しいファンアウト経路の両方に互換である必要があった
- バグや障害が発生し得る範囲が広がり、マイグレーションが長引くほど運用リスクも増えた
- 互換性問題によって小規模な障害が数回発生し、迅速かつ安全に移行を終える必要性が高まった
- 初期には、各プロダクトチームが自ら通知マイグレーションの優先順位を決めるボトムアップ方式を採った
- 6か月で約**20%**を移行するにとどまった
- プロダクトロードマップや他チームの優先事項に押され、進捗が遅くなった
- エンジニアリングリーダーシップが2026年第1四半期完了に合意し、明確な期限と全社優先順位を与えたあと、残りのマイグレーションを6週間で完了した
- 全体の作業には10チーム、30人以上のエンジニアが参加した
- Notionトラッカーと自動化で進捗を管理した
- ステークホルダーと頻繁にコミュニケーションし、作業を複数チームに分散した
- プラットフォームチームが約半数のマイグレーションを実施したあと、通知オーナーチームにデプロイと整理を引き渡した
- 通知が多いチームに作業が集中しないよう負荷を分散した
- オフィスアワー、リーダーボード、賞品、ハッピーアワーを含む通知マイグレーション週間を開催し、進行を加速して完了を祝った
- 全体期限の2週間前に引き渡しを終えるバッファ期間を確保した
- 追加支援が必要なチームを助け、コードベースから漏れている作業を検索した
- 最終点検で新たに必要なマイグレーション4件と複数の未完了項目を見つけたが、スケジュールへの影響は小さいまま完了した
大規模プラットフォーム移行から得た教訓
- 明示的な優先順位がなければ、複数チームにまたがるマイグレーションは各チームのロードマップと競合し、簡単に後回しになる
- スケーリング効果が目立ちにくい低容量通知のロングテール部分で特に顕著だった
- リーダーシップの合意、共同優先順位、明確な期限が、妥当な期間内に移行を終えるうえで重要だった
-
プラットフォーム作業はAPIで終わらない
- 以前は、他チームの通知に関する質問や配信問題を調査することが、プラットフォームエンジニアのオンコール業務のかなりの部分を占めていた
- 可観測性、アラート、ダッシュボード、デバッグツールも併せて改善してこそ、プロダクトおよびサポートチームが独立して調査でき、プラットフォームのオンコール負荷も減らせる
- 現在のボトルネックだけでなく、次のボトルネックも考慮する必要がある
- 1つのプロジェクトですべての通知スケーリング問題を解決することはできなかった
- 将来機能を受け入れられるよう設計することで、通知量が増えたときにさらなる大規模リファクタリングが必要になる可能性を下げた
次のボトルネックと今後の計画
- 通知生成はスケールしたが、次のボトルネックは受信者リスト生成である
- 通知対象が拡大し続ければ、数百万人の受信者を効率よく取得することがますます重要になる
- 受信者リスト生成をNotification Factoryに含め、プラットフォームがエンドツーエンドの通知フローをより多く担うようにする計画だ
- 通知設定も、開発者とファンの双方に向けて改善する予定だ
- 開発者が新しい設定をより簡単に作成・管理できるよう支援する
- ファンが通知の好みをより簡単に理解し、制御できるよう改善する
まだコメントはありません。