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.

Listing 1: Sending a message to a single device

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.

Listing 2: Sending a message to a list of devices

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.

Listing 3: Sending a collection of messages as a batch

The 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 sendMulticast() and 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 successCount and 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.

Listing 4: Performance comparison of send and sendAll APIs

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!