■ 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%削減