Old architecture bottleneck and how I've dealt with that

Old architecture bottleneck and how I've dealt with that
Photo by Jeffrey Blum / Unsplash

React native is cool! I love it but this love is toxic. As I love it, I hate it as well. But this thing is being developed for over than 10 years and so should we do. This post is not really actual for modern architecture of RN but I find this case interesting. So let's start!


What is old architecture? We have JS side and we have native side. What does it mean? We write JS code. JS code is running inside the app but it should manipulate with app organs: buttons, vibrations, data storage, basically anything that the app can do. So in JS code of our react native app we make calls to the side of the app that is not made in JS. We call it native side. And how do these calls reach the native side? Here is where the React Native Bridge (RNBridge) comes. Imagine that you have constant connection between web front end and backend. Front and back sending small JSONs to each other with information of what is happening in backend and frontend. In basics RNBridge works alike but it's not network connection. It's just a queue of JSONs going in both directions. And each call to the native side or back produces this little json and sends it to the other side

What's the most important is that these calls are async.

So here it is: The Bottleneck! How can we break everything with that architecture? DDOS it! And that's what we did in our mobile app where I've been working for the last years. In the end of onboarding to this project they said: "Yeah! And we should do something with it some day". So what was exactly the case is?

The tool for storing data in RN applications between sessions is called AsyncStorage. It goes out-of-the-box of RN. It's like localStorage but yeah, that's async. Everything is async with RNBridge. In this projects we've been using it in state-like style with hooks, alike to useState, but useCustomAsyncStorage.

createStorage(key) works straight with AsyncStorage and exposes methods for usage is useState style. Also there is subscription model to notify everyone in the room who works with this key at this moment. As you can see there are calls in useEffect, we getItem and setValue to state. And this hook is used in like hundreds places in the app. Every time we call it, it starts sending request to native side via AsyncStorage immediately.

This was catastrophic! Every start of the app these isolated calls of this hook DDOSed RNBridge! We saw an warning in a console:

Excessive number of pending callbacks: 501

With a lot of "AsyncStorage.getItem" mentions. Actually such mentions lead me to understanding the issue eventually. So I needed to

  • Get rid of spam into th RNBridge
  • Still provide everyone with data form AsyncStorage and let everyone to write in AsyncStorage
  • Do not rewrite the whole thing! Leave the usage of the hook

It took me quite some time to get to the decision: we need to prefetch data and do not block the app during this fetch. So what I did:

  1. Created a cache. It's a new Map<string, string | null>()
  2. Created a Promise for prefetching the data
  3. Created ensurePrefetch function where we get all data and cache it.

As far as our hook for working with AsyncStorage is very async at the deepest layer of it's implementation we can work asynchronously! At the very top of the app we ensurePrefetch() all data and assign this fetch to Promise from step 2. We call this Promise immediately after it's creation. And we "await ensurePrefetch()" it at the beginning of low-level getter.

Before it: getter was doing await AsyncStorage.getItem(key)
After:

As you can see here we still have fallback option to go straight to AsyncStorage. The rest I should do is make sure the cache is always up-to-date.

Conclusion:
This small fix improved app start time in more than 10 times. UI thread used to be blocked for some significant time but now this time was barely noticeable. We still have a lot of optimisation steps to do but this one is small and effective.