/note/tech

ポーリング処理廃止によるイベント駆動アーキテクチャへの移行

要約:

■ 1. 既存アーキテクチャと課題

  • 各クライアントが30秒間隔でポーリングを実施し、1回あたり9回のAPIコールが発生
  • 予約検索APIで約2,000 req/秒の高負荷が生じている
  • 課題として高負荷、スケーラビリティへの懸念、非効率なリソース利用の3点が存在

■ 2. 問題の本質

  • ポーリングはデータ変更の有無にかかわらず定期的にリクエストが発生する
  • 無駄なリクエストが多いことがポーリングの根本的な問題

■ 3. サーバープッシュ型通信方式の比較

  • WebSocket:
    • リアルタイム性が高く、双方向通信が可能で低レイテンシ
    • 通知用途には過剰機能であり、接続管理が複雑でスケーリングが難しい
  • Server-Sent Events:
    • ブラウザネイティブ対応でHTTP/1.1で動作し、自動再接続機能を持つ
    • 接続管理が複雑でスケーリングが難しい
  • gRPC Server Streaming:
    • 型安全でバイナリ効率が良く、モバイル/BFF構成と相性が良い
    • ブラウザはgRPC-Web経由のため導入コストが高く、接続管理が複雑でスケーリングが難しい
  • 3方式に共通する課題:
    • 接続がPodに固定されるため、別Podに届いたイベントを他Podの接続クライアントへ転送する仕組みが別途必要

■ 4. 新アーキテクチャ: Firestoreを用いたイベント駆動設計

  • Firestoreを採用した理由:
    • SDKのみで接続管理なしにリアルタイム同期が実現可能
    • クライアント数の増加に対してインフラ側での対応が不要
    • 薬局ごとのドキュメント分離により、各薬局が自身に関係するドキュメントのみを購読できる
    • GKEやCloud Pub/Subをすでに使用しており、同じGoogle Cloudエコシステム内で完結できる
  • 新アーキテクチャのフロー:
    • 予約サービスがイベントを発火し、Cloud Pub/Subにメッセージを配信
    • イベント伝播サービス(GKE)がCloud Pub/Subを購読
    • イベント伝播サービスがFirestoreの薬局別ドキュメントを更新
    • フロントエンドが薬局別ドキュメントを監視し、Firestoreの変更をリアルタイム検知
    • 変更検知後、予約取得APIから予約データを取得

■ 5. 追加課題と解決策: 短時間における大量イベントへの対処

  • 課題:
    • 短時間に多数のイベントが発生した場合、そのままクライアントへ伝播すると都度APIが呼び出される
    • スロットリングなしでは0.3秒間に4件のイベントが発生した場合、4回のAPI呼び出しが生じる
  • 解決策:
    • Cloud Tasksを用いたスロットリング機構を導入
    • 10秒間隔のウィンドウ内に発生した複数のイベントを1回の通知にまとめる
    • 同一ウィンドウ内の後続イベントはタスク登録をスキップし、APIリクエストの集中を回避

■ 6. 移行後の結果

  • 定性的な改善:
    • 予約一覧更新のリアルタイム性が向上
  • 定量的な改善:
    • 予約検索APIの秒間リクエスト数が約2,000/秒から約1,000/秒へ50%削減
    • DBコストが100%から70%へ30%削減