Firebase: Introducing the Cloud Messaging batch APIs in the Admin SDK
Since the Firebase Cloud Messaging (FCM) support became available across all flavors of the Admin SDK, many developers have been requesting for an API that enables sending messages to more than one recipient. The original FCM API, which is demonstrated in listing 1, facilitates sending messages to individual devices by addressing each message to a single device registration token.
But developers often come across scenarios where a certain message needs to be sent to multiple devices. I myself ran into this use case when I was working on my cryptocurrency price monitor app. My server-side code that watches for cryptocurrency price updates has to notify users of the price changes, and often it has to notify multiple users. Sending one message at a time is quite inefficient when you have hundreds if not thousands of users to notify.
One way to implement this use case is to employ the pub-sub messaging style. You can subscribe an arbitrary number of devices to an FCM topic, and then address your messages to the topic using the Admin SDK. FCM topics are optimized for throughput, and you can efficiently send messages to millions of devices this way. However, there are still many scenarios where pub-sub may not be the answer. For example in my cryptocurrency app, the set of devices to be notified is different every time. For each price change, my application executes a Firestore query to determine the set of devices that should be notified. FCM topics are not very useful in situations like this where the set of target recipients is dynamic. Moreover, with the pub-sub messaging paradigm you should also think about how to handle and maintain the topic subscriptions over time. This is extra work when all you want is to run a database query, and notify the returned devices.
In order to better support sending messages to multiple FCM recipients, Firebase Admin SDK recently introduced a couple of new APIs. First of these is the
sendMulticast() method, which supports sending a message to a list of devices. As shown in listing 2, you can use this API to send a message up to 100 devices using their device registration tokens.
The other new API added to the Admin SDK is the
sendAll() method. This method accepts a list of up to 100 arbitrary messages, and sends them all as a single batch. The payload of each message can be different, and they can also be addressed to any combination of device tokens, topics and condition statements. Listing 3 shows how this API can be used to send a customized set of messages, each addressed to a different recipient.
sendAll() method makes only one RPC call to deliver the entire batch of messages. It’s worth noting that the
sendMulticast() API described earlier is also implemented on top of the
sendAll() API. The SDK creates a list of messages from the given
MulticastMessage object, and calls the
sendAll() method under the hood. This is why the upper limit of messages is 100 in both cases (this limit may be increased in the future). For the same reason, both
sendAll() methods have the same return type —
BatchResponse. You can inspect this object to check the success/failure status of each message in a batch. Or you can query the
failureCount attributes of the return value to get a quick summary of the result.
The new FCM APIs are great when you wish to send messages to hundreds or even thousands of users. Listing 4 shows a simple test that uses the
send() method and the new
sendAll() method to deliver a list of 1000 messages. In each test, we start a set of concurrent tasks, and wait for them to finish. Therefore in case of the
send() method, we are not actually sending one message after another. That will obviously be much slower than the
sendAll() method, and won’t be a fair comparison. Instead, we measure the time it takes to finish 1000 concurrent send operations.
In my local test environment, the implementation that uses the
send() API takes around 2750 ms on average, while the implementation that uses the
sendAll() API takes only 830 ms. I also repeated the experiment with the message count increased to 10000. Still the
sendAll() API manages to hand off all 10000 messages to FCM in about 1400 ms. But the
send() API fails with I/O errors in this case due to the high volume of concurrent RPC calls.
If we keep increasing the number of messages, we will eventually run into I/O errors with the new FCM APIs as well. Therefore, if your app needs to regularly send out messages to millions of users, topic messaging should be the preferred option. Alternatively, you can consider using the new APIs in combination with some local throttling to make sure your runtime doesn’t start more concurrent I/O operations than it can handle. Having said that, these new FCM APIs should still be a huge step up for many applications that only need to send messages to several thousand users at a time.
The new FCM APIs are now available in the Node.js and Java flavors of the Firebase Admin SDK. Check the documentation for more details and examples. If you have any feedback or suggestions related to this feature, please feel encouraged to share them on the Firebase GitHub repos. Happy coding with Firebase!