大量のPush通知をCloud Functions経由で送信する

実験の様子

こんにちは。ふぁぼ通知が好きなうなすけです。

モバイルアプリでは、Push通知によってユーザーに情報を伝えたり、行動を促したりすることが日常的に行われています。

その通知を送る対象のユーザーが数十人程度の規模なら、愚直に一通一通送信すればいいでしょうが、一気に万単位のユーザーにPush通知を送信したい場合、愚直に送信すると完了までに数時間かかることも往々にしてあります。

CASHでの大量Push通知事例

pull req

以前、ある施策のために42万件のPush通知を送る必要がありました。CASHではFirebase Cloud Messaging(以下FCM)をPush通知の送信に使用しているのですが、このとき、通知1件ごとにAPIを叩いていく方式だと送信完了に1日かかってしまうという事態になりました。

FCM Topic Messaging

そのような場合に使用できるのが、Topicを用いた通知の送信です。Topicを使用すると、そのTopicを購読している端末に一度にPush通知を送ることができます。実際、42万件のPush通知は最終的にTopicによって送信しました。

Topicの利点と欠点

Topicを使用する場合、あらかじめユーザー(というよりはデバイストークン)をTopicに対して登録させなければなりません。Firebaseのドキュメントに記載されている「荒天警報」のような、事前に通知の対象がわかっている場合は使い勝手が良いです。 しかし弊社の場合は、急に「こういうユーザーに対してこういう通知を送ってみたい!」という 実験 が提案され、対象となる大量のユーザーIDが渡される、ということがままあります。そのような場合、Topicを新規に作成しなければならず、それに時間がかかること、Topic自体も作成から使用可能までに最大で1日の遅れが生じることから、「即・大量に!」というユースケースには使い勝手があまりよくありませんでした。

既存の実装で使えるものはないか?

まずは、インターネット上に大量にPush通知を送信するためのプロダクトがないかどうか調べることにしました。すると、Mercariさんでの事例がOSSとして公開されていることを見つけました。

tech.mercari.com

github.com

「これじゃん!」と思い導入しようと、まずソースコードを読んでみました。すると、GaurunはiOSはAPNs、AndroidGoogle Cloud MessagingもしくはFirebase Cloud Messagingを使用するような実装になっていました。 CASHでは、iOSAndroidもFCMを使用してPush通知を送信しています。Gaurunを、両OSでFCMを使用するように変更することは不可能ではありませんでしたが、既存のAPIを変更せずに、ということが結構難しそうだった1ので、何か別のやりかたを探すことにしました。

FaaSを用いたPush通知

そこで、Faasを使うことを思い付きました。もともとPush通知というタスクは、複雑な処理をしない小さなものであり、単発のタスクなので、FaaSに乗せるにはもってこいの題材でしょう。

また、ちょうどいいタイミングでGoogle Cloud FunctionsがNode.js 8 Runtimeをサポートし始めたので、async/awaitも使えるしやってみようということになりました。2

The Node.js 8 Runtime  |  Cloud Functions Documentation  |  Google Cloud

Promiseを用いた並列実行

Cloud Functionsを使うことにしたところで、Push通知1件に対して1 function callでは、以前の問題を解決できているとは言えません。

なので、対象となるデバイストークンを一気に投げて、その大量のトークンを分割したものを自分自身に送信する、という再帰的な処理を行なうことで大量のデバイスに対してのPush通知システムを作成しました。

github.com

結果

幸か不幸か、数万規模の通知を送ることが直近は無さそうした。なので社内の有志を募り、その人の端末に向けて大量の通知を送ってみることにしました。下記のスクリーンショットはそのときの様子です。

このときは14端末に向けてそれぞれに500通、合計7000件のPush通知を送信しました。送信完了までにかかった時間は約1分間でした。

実験の様子

これが高速なのかそうでないのかは、他の通知基盤がどのようなスペックかわからないので何も言えません。しかし手動で実行したrequest自体は1回で、そのresponseは一瞬で返ってきていることを考えると、これまでの手間が大幅に削減されたことは明白です。

問題点

呼び出し上限に達してしまう

そりゃそうだろ、という感じではあるのですが、自身を再帰的に呼び出すので一瞬にして実行数が跳ね上がり、quota limitに引っ掛かります。

Quotas  |  Cloud Functions Documentation  |  Google Cloud

一応、Function invocations per second は申請によって上限を緩和させることができます。しかしなるべくなら初期上限のままで運用できるような仕組みにしたいです。案としては再帰実行前にジッタを挿入して呼び出しタイミングをずらすというものがあります。

他には、API limitation の存在しないFaaSを使用するという手もあります。いや、そんなものがあるのか?となりましたが、Knativeというものがありますね。これは確かに無制限に呼び出せますが……

その辺りの話を今度弊社が開催するイベントで話す予定なので、皆さん是非参加してください。

bank.connpass.com

Push通知の全てのパラメータに対応していない

ひとまずCASHで使用するパラメータに絞って実装したために、必要最小限のパラメータにしか対応していません。

まとめ

今回、FaaSによるPush通知基盤という、ほぼ実験的な手法を試してみました。結果、これまでの作業時間を大幅に短縮することに成功しましたが、API呼び出し回数制限などの新な課題も発見しました。

BANKでは、このような実験的なアプローチを、ビジネスモデルだけでなく、技術的な部分にも実践していく企業です。興味があれば、是非お声掛けください!

bank.co.jp


  1. 独自Forkを自社で運用することもできますが、せっかくなら上流に還元したいし、メンテはしたくないので……

  2. 結局 async/awaitは使いませんでしたが。