Image for post
Image for post

Firebase: Python Admin SDK with asyncio

Python 3.4 introduced the asyncio module for implementing single-threaded concurrent code and multiplexing I/O using coroutines. Developers can use asyncio and the associated helper modules such as aiohttp to implement highly scalable, non-blocking I/O. However, modules that make blocking I/O calls (e.g. requests, urllib) often do not work well with asyncio. Firebase Python Admin SDK, which is based on requests, is also affected by this deficiency. One could simply invoke Admin SDK methods from a coroutine as shown in listing 1, but the result is not fully asynchronous.

Listing 1: Bad usage of Admin SDK with asyncio

As soon as an I/O operation is invoked by the SDK (ref.get() in the case of listing 1), the whole program blocks until it completes. As a result the above code takes about 19 seconds to complete, when executed on my Mac over home WiFi.

Libraries like firebase_admin and requests still maintain Python 2.7 compatibility. Therefore it will be a while before native asyncio support is provided in them. But in the meantime there is a simple trick developers can employ to use these libraries with asyncio, while preserving the full asynchronous nature of the applications. The key to this trick is to use the run_in_executor() method provided by the asyncio event loop. Listing 2 illustrates this point.

Listing 2: Correct usage of Admin SDK with asyncio

Listing 2 first initializes a thread pool executor with 20 worker threads. Then instead of directly invoking Admin SDK operations from the coroutine, it delegates them to the thread pool. This results in a Future that can be waited on using typical asyncio primitives like yield from or await. This ensures that none of the coroutines started by the code gets blocked on I/O, thus allowing multiple Firebase database interactions to run in parallel. Consequently, listing 2 completes under 2 seconds in my test environment — a near 10X speedup!

Now of course this is not perfect, with the use of “threads” being a major caveat. Therefore this trick cannot possibly scale as well as a coroutines-only solution. In fact increasing the number of workers in the thread pool is the only way to scale this, which is not ideal. But it will get the job done. It certainly enables us to use firebase_admin and a host of other classic Python libraries in conjunction with asyncio. All in all, developers should be able to get quite a bit of mileage from this technique until native asyncio support becomes more prevalent in the Python ecosystem.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store