Slide 1

Slide 1 text

Firebase Cloud Messagingにおける 通知速度のボトルネックの調査と改善 Nikkei Tech Talk 2025/11/13 日本経済新聞社 夏坂友輔 1

Slide 2

Slide 2 text

● 夏坂友輔 ● https://github.com/ntsk ● 日経電子版・日経紙面ビューアーアプリ(主にAndroid)やモバイル向け のBFFおよび周辺環境の開発を担当 whoami 2

Slide 3

Slide 3 text

日経電子版・紙面ビューアー 3

Slide 4

Slide 4 text

日経電子版アプリでは、Firebase Cloud Messaging(以下FCM)を利用し て通知の配信を行なっています。 2018年頃からFCMに移行し7年ほどになりますが、長期で運用する中で配信 速度の遅延が目立つようになってきました。 本日はそのボトルネックの特定と解消までのプロセスについて紹介します。 本日話すこと 4

Slide 5

Slide 5 text

1. FCMを利用した通知の仕組みをおさらい 2. 日経電子版アプリでのFCMの利用状況 3. ボトルネックの調査 4. 実装の改善と段階的移行 5. 結果と今後 6. まとめ アジェンダ 5

Slide 6

Slide 6 text

FCMを利用した 通知の仕組みをおさらい 6

Slide 7

Slide 7 text

● https://firebase.google.com/docs/cloud-messaging ● Google が提供する無料のプッシュ通知サービス ● 下記の2つの主要なコンポーネントを提供 ○ FCMバックエンド ■ メッセージリクエストからメッセージのデータを生成するバックエンドサーバー の提供 ■ Firebase Admin SDK(Node, Java, Python, C#, Go)やFCM APIを利 用したFCMサーバーとの対話 ○ FCMクライアント ■ iOS, Android, Web, Flutter, Unity, C++などに対応 Firebase Cloud Messaging 7

Slide 8

Slide 8 text

https://firebase.google.com/docs/cloud-messaging/fcm-architecture 8

Slide 9

Slide 9 text

● トークンベース ○ 配信したいデバイスに対してFCMトークンを指定して送信する ○ 呼び出しごとに500個までトークン指定が可能 ● トピックベース ○ 特定トピックにオプトインした複数デバイスにメッセージを送信する ○ トピックの上限は2000まで ● デバイスグループベース (※Admin SDKでは未対応) ○ グループ毎に発行したキーをベースに送信する ○ 1つのキーに紐付けられるトークンは20まで メッセージ配信方法の種類 9

Slide 10

Slide 10 text

特定のデバイスへ送信 registration_token = 'YOUR_REGISTRATION_TOKEN' message = messaging.Message( data={ 'score': '850', 'time': '2:45', }, token=registration_token, ) response = messaging.send(message) registration_tokens = [ 'YOUR_REGISTRATION_TOKEN_1', # ... 'YOUR_REGISTRATION_TOKEN_N', ] message = messaging.MulticastMessage( data={'score': '850', 'time': '2:45'}, tokens=registration_tokens, ) response = messaging.send_each_for_multicast(message) 複数デバイスへ送信 トークンベースの実装例 (Python) 10

Slide 11

Slide 11 text

特定のトピックに対してトークンを購読/解除 トピックベースの実装例 (Python) registration_tokens = [ 'YOUR_REGISTRATION_TOKEN_1', # ... 'YOUR_REGISTRATION_TOKEN_n', ] # Subscribe the devices corresponding to the registration tokens to the topic response = messaging.subscribe_to_topic(registration_tokens, topic) # Unsubscribe the devices corresponding to the registration tokens to the topic response = messaging.unsubscribe_from_topic(registration_tokens, topic) 11

Slide 12

Slide 12 text

トピックを購読しているデバイスへ送信 トピックベースの実装例 (Python) topic = 'highScores' message = messaging.Message( data={ 'score': '850', 'time': '2:45', }, topic=topic, ) # Send a message to the devices subscribed to the provided topic. response = messaging.send(message) 12

Slide 13

Slide 13 text

デバイスグループを作成 デバイスグループベースの実装例 (HTTPS) POST https://fcm.googleapis.com/fcm/notification Content-Type:application/json access_token_auth: true Authorization: Bearer ya29.ElqKBGN2Ri_Uz...HnS_uNreA project_id:SENDER_ID { "operation": "create", "notification_key_name": "appUser-Chris", "registration_ids": ["bk3RNwTe3H0:CI2k_HHwgIpoDKCIZvvDMExUdFQ3P1...", "cR1rjyj4_Kc:APA91bGusqbypSuMdsh7jSNrW4nzsM...", ... ] } { "notification_key": "APA91bGHXQBB...9QgnYOEURwm0I3lmyqzk2TXQ" } REQUEST RESPONSE 13

Slide 14

Slide 14 text

tokenフィールドにグループキーを設定して送信 デバイスグループベースの実装例 (HTTPS) POST https://fcm.googleapis.com/v1/projects/myproject-b5ae1/messages:send Content-Type: application/json Authorization: Bearer ya29.ElqKBGN2Ri_Uz...HnS_uNreA { "message":{ "token":"APA91bGHXQBB...9QgnYOEURwm0I3lmyqzk2TXQ", "data":{ "hello": "This is a Firebase Cloud Messaging device group message!" } } } REQUEST 14

Slide 15

Slide 15 text

日経電子版アプリでの FCMの利用状況 15

Slide 16

Slide 16 text

通知の活用内容と配信方法 ● トークンベース ○ 特定のユーザーの最適化してセグメ ント配信する通知や、法人向けの個 別通知に利用 ● トピックベース ○ 朝刊・速報などの機械的に配信する 通知に利用 16

Slide 17

Slide 17 text

トークン登録 ~配信のフロー 配信ツール アプリAPI 🔔 ③Push通知 送信指示 ④Push通知 送信指示 ⑤Push通知 送信 ①通知許諾・トークン登録リクエスト ②トピック購読トー クン登録 Firebase 17

Slide 18

Slide 18 text

● 通知が届くのが遅いとの声が 社内でたびたび上がる ● 表示されるまでに5分以上か かっているユーザーも ● 開封率や許諾率などは追って いたが、速度は詳細に追えて いなかった 通知遅延が目立つように 🚨 速報性の高いニュースを即座に届けることできない...🚨 18

Slide 19

Slide 19 text

ボトルネックの調査 19

Slide 20

Slide 20 text

通知の遅延には様々な要因がありえる 自社バックエンドの性能 FCMサーバーの性能 サーバー間通信の遅延 ユーザーのネットワーク環 境 OSの電源管理 通知の優先度設定 UIの描画遅延 20

Slide 21

Slide 21 text

通知表示までのプロセスを分解して計測 通知が端末上で表示されるまでの各プロセスで時刻を計測し、遅延が発生し ている箇所を特定する。 1. 自社APIが通知を送信した時刻 2. Firebaseサーバーが通知を送信した時刻 3. トランスポート時の遅延有無 4. アプリが通知を受け取った時刻 5. 端末上で通知が表示された時刻 21

Slide 22

Slide 22 text

自社APIが通知を送信した時刻 配信ツール アプリAPI 🔔 ③Push通知 送信指示 ④Push通知 送信指示 ⑤Push通知 送信 ①通知許諾・トークン登録リクエスト ②トピック購読トー クン登録 Firebase 22

Slide 23

Slide 23 text

自社APIが通知を送信した時刻 ● 送信時に通知のペイロードにタイムスタンプをつけておき、アプリに届いた 後に届いた時間と共に計測基盤へ送信して差分をとる。 ● 後述のFirebaseサーバーの送信時刻と比較することでBFFサーバー ↔FCMサーバー間での遅延がないか確認する。 23

Slide 24

Slide 24 text

Firebaseサーバーが通知を送信した時刻 配信ツール アプリAPI 🔔 ③Push通知 送信指示 ④Push通知 送信指示 ⑤Push通知 送信 ①通知許諾・トークン登録リクエスト ②トピック購読トー クン登録 Firebase 24

Slide 25

Slide 25 text

Firebaseサーバーが通知を送信した時刻 ● Androidの場合には、SDKのRemoteMessage.sentTime から取得 が可能 ○ https://firebase.google.com/docs/reference/android/com/google/fire base/messaging/RemoteMessage#getSentTime() ○ iOSでは同値のプロパティはない。Flutterなどのクロスプラットフォームでは iOSの み0になるとの報告がある。 25

Slide 26

Slide 26 text

Firebaseサーバーが通知を送信した時刻 ● BigQuery データエクスポートを有効にすると、FCMで発生した各イベン トのログを確認できる ○ https://firebase.google.com/docs/cloud-messaging/understand-deli very?platform=ios#enable-message-delivery-data-export 26

Slide 27

Slide 27 text

Firebaseサーバーが通知を送信した時刻 BigQuery データエクスポートで調査に活用できるフィールド (抜粋) event_timestamp TIMESTAMP サーバーにより記録されたイベントのタイムスタンプ。 app_name STRING Android アプリのパッケージ名または iOS アプリのバンドル ID。 topic STRING メッセージが送信されたトピックの名前(該当する場合)。 event STRING ● MESSAGE_ACCEPTED: メッセージは FCM サーバーによって受信され、リクエストは有効です。 ● MESSAGE_DELIVERED: メッセージはデバイス上のアプリの FCM SDK に配信されました。デフォルトで は、このフィールドは伝播されません。有効にするには、 setDeliveryMetricsExportToBigQuery(boolean) の手順に従ってください。 ● etc… analytics_label STRING メッセージの送信時にアナリティクス ラベルを設定して、そのメッセージをアナリティクス用としてマークできます。 https://firebase.google.com/docs/cloud-messaging/understand-delivery?platform=ios#what-data-exported 27

Slide 28

Slide 28 text

Firebaseサーバーが通知を送信した時刻 SELECT TIMESTAMP_DIFF( MAX(delivered_time), MIN(accepted_time), MILLISECOND ) AS latency_ms FROM ( SELECT event_timestamp AS accepted_time FROM `project ID.firebase_messaging.data` WHERE _PARTITIONTIME = TIMESTAMP('date as YYYY-MM-DD') AND message_id = 'your message id' AND instance_id = 'your instance id' AND event = 'MESSAGE_ACCEPTED' ) sent CROSS JOIN ( SELECT event_timestamp AS delivered_time FROM `project ID.firebase_messaging.data` WHERE _PARTITIONTIME = TIMESTAMP('date as YYYY-MM-DD') AND message_id = 'your message id' AND instance_id = 'your instance id' AND (event = 'MESSAGE_DELIVERED' ) delivered; 遅延を調査するクエリの例 28

Slide 29

Slide 29 text

トランスポート時の遅延有無 配信ツール アプリAPI 🔔 ③Push通知 送信指示 ④Push通知 送信指示 ⑤Push通知 送信 ①通知許諾・トークン登録リクエスト ②トピック購読トー クン登録 Firebase 29

Slide 30

Slide 30 text

トランスポート時の遅延有無 ● AndroidのみFirebase Cloud Messaging Data API を利用すること で、Android SDKにおける配信指標を確認可能 ○ https://firebase.google.com/docs/cloud-messaging/understand-deli very#message-delivery-reports ○ ただしレポートの反映まで最大24hの遅延がある 30

Slide 31

Slide 31 text

トランスポート時の遅延有無 { "appId": "1:23456789:android:a93a5mb1234efe56", "date": { "year": 2021, "month": 1, "day": 1 }, "analyticsLabel": "foo", "data": { "countMessagesAccepted": "314159", "messageOutcomePercents": { "delivered": 71, "pending": 15 }, "deliveryPerformancePercents": { "deliveredNoDelay": 45, "delayedDeviceOffline": 11 } } 31

Slide 32

Slide 32 text

トランスポート時の遅延有無 ● iOSについてはAPNs経由になるため確認ができないが、Firebaseコン ソール上のレポート画面から送信数を確認することでAPNsサーバーに正 常に渡されているかは確認できる。 32

Slide 33

Slide 33 text

アプリが通知を受け取った時刻 配信ツール アプリAPI 🔔 ③Push通知 送信指示 ④Push通知 送信指示 ⑤Push通知 送信 ①通知許諾・トークン登録リクエスト ②トピック購読トー クン登録 Firebase 33

Slide 34

Slide 34 text

アプリが通知を受け取った時刻 ● 通知を受け取ったタイミングとユーザーに通知が表示されるタイミングは 厳密には異なる可能性がある ○ 特にData Message方式で送信している場合には自前で通知の表 示を実装する必要があるため、受信から表示までに行っている処理 の影響で遅延が発生する可能性がある。 34

Slide 35

Slide 35 text

アプリが通知を受け取った時刻 ● 前述のBigQuery データエクスポートを有効にしている場合、 MESSAGE_DELIVERED フィールドを利用してこの時刻を特定できる。 ○ このフィールドを利用するためには、アプリ側の SDKで setDeliveryMetricsExportToBigQuery(boolean) を有効にする必要があ る。 35

Slide 36

Slide 36 text

端末上で通知が表示された時刻 配信ツール アプリAPI 🔔 ③Push通知 送信指示 ④Push通知 送信指示 ⑤Push通知 送信 ①通知許諾・トークン登録リクエスト ②トピック購読トー クン登録 Firebase 36

Slide 37

Slide 37 text

端末上で通知が表示された時刻 ● ユーザーに実際に表示されたタイミングを計測する ● 受信から表示までに特に処理が行われていない場合には、受信と同様に 扱っても良いが、そうでない場合には考慮が必要 37

Slide 38

Slide 38 text

原因の特定 ● 計測した結果、FCMから送信される時間が遅延していることが確認でき た。 ● この現象についてFirebase へ問い合わせてみると、1つのトピックに対し て購読しているFCMトークンの数が膨大になっており配信に時間がか かっている可能性があるとの回答をいただいた。 ○ 100万トークンに対して配信を試みた場合、最初に届く通知に数分かかり最後に届 く通知が数時間かかる可能性もある。 ○ 開発環境では数が少ないので再現できなかった。 38

Slide 39

Slide 39 text

実装の改善と段階的移行 39

Slide 40

Slide 40 text

実装の改善 1トピックあたりの購読数を削減するには... 1. 使われていないトークンの購読を解除する 2. トピックを分割する 40

Slide 41

Slide 41 text

● FCM登録トークン管理のベストプラクティスとしてドキュメントで紹介されて いる。 ○ https://firebase.google.com/docs/cloud-messaging/manage-tokens ● バックエンドでFCMトークンをタイムスタンプ情報と共にデータストアへ格納し ておき、特定の期間で閾値を設定しそれ以前のトークンを削除する。 使われていないトークンの購読を解除する 41

Slide 42

Slide 42 text

使われていないトークンの購読を解除する アプリAPI Push通知 送信指示 Push通知 送信 Firebase 使われていない端末が送信対象に含まれることで、トピックに対する母数が増 えて通知が遅延する。 42

Slide 43

Slide 43 text

使われていないトークンの購読を解除する アプリAPI Push通知 送信指示 Push通知 送信 Firebase 例えば、1ヶ月以上使われていない端末のトークンをバックエンドで自動で購読 解除するようにする。期間はサービス要件によって速度とトレードで調整。 43

Slide 44

Slide 44 text

● 同様の内容を配信するトピックであっても、地域やユーザー属性によって 別のトピックを購読させるようにする。 ● トピックは2000まで作ることが可能なため、この範囲内で分割する。 トピックを分割する アプリAPI Push通知 送信指示 Push通知 送信 Firebase topicA1 topicA2 topicA3 44

Slide 45

Slide 45 text

電子版アプリのトピック運用における課題 ● トークンを定期的に削除するプラクティスは、2018年実装当時ドキュメントが 公開されておらず、定期的に削除する実装自体がないまま運用していた。 ○ 2024年5月15日移行は270日以上経過したトークンを自動で削除する対 応がFCM側で行われている。 ○ しかし、この期間のトークン全てに対して送信を試みようとすることで遅延 につながっている。 ● トークンを特定して削除しようにも、具体的なトークンがFirebase側にしかな いため、削除できない。 45

Slide 46

Slide 46 text

定期削除の仕組みを実装 アプリAPI 🔔 Push通知 送信指示 Push通知 送信 通知許諾・トークン登録リクエスト トピック購読トーク ン登録 Firebase トークン管理 DB・JOB 通知許諾・トークン登録 古いトークンを 定期削除 46

Slide 47

Slide 47 text

● ⚠ 古いトピックにはどれだけのトークンが購読されているかも分からな い。新しく管理し始めたとして古いトークンが削除できない限り速度は改善 しない。 ○ このため、トークンが管理されている新しいトピックを作成し、段階的に移 行する。 トピックを段階的に移行 47

Slide 48

Slide 48 text

● ⚠ 通知が届かない、通知が2重で届くといったことが発生するとインシデ ントとなる。 ○ これを回避するため、旧トピックをV1、新しいトピックをV2として、段階 移行期間中はV1とV2両方を購読、V2を購読している場合にはV2に 送信、そうでない場合はV1に送信する。 ○ また、Firebase Remote Configを利用して時間をかけて段階的に V2の購読を進めるようにした。 トピックを段階的に移行 48

Slide 49

Slide 49 text

● V1 / V2の送信を分けるには、conditionを利用。 ○ 'TopicV2' in topics なら V2に送信 ○ !('TopicV2' in topics) && 'TopicV1' in topics ならV1に送信とする ○ https://firebase.google.com/docs/cloud-messaging/send-topic-mess ages トピックを段階的に移行 49

Slide 50

Slide 50 text

結果と今後 50

Slide 51

Slide 51 text

1分以内におおよそ 90%のユーザーが受信 従来 受信までの時間分布 (分) 改善後 受信までの時間分布 (分) 51

Slide 52

Slide 52 text

● ほとんどのユーザーが1分以内に受信できるようになった 👏 ● しかし、もっと速くできる余地はある。 ○ トピックの分割の他、期限による管理のバランスを調整することで、よ り高速に通知が届くよう継続してチューニングしていきたい。 1分以内におおよそ 90%のユーザーが受信 52

Slide 53

Slide 53 text

まとめ 53

Slide 54

Slide 54 text

● サービスの成長や時間経過とともに、性能は劣化する可能性がある。 SaaSを利用している場合でも劣化の発生を検知できるような仕組みづくり に取り組むことが重要。 ● FCMにはブラックボックスな部分も多いが、調査するための仕組みも様々 提供されており、プラクティスも過去と比べて増えている。定期的にドキュ メントやプラクティスを見直すことも必要。 まとめ 54

Slide 55

Slide 55 text

EOF 55