Swiggy
Shubham Singla
Today, I will walk you through a fascinating story about how we scaled up the Swiggy Restaurant Android app from a single-restaurant order flow feature to a multi-restaurant order flow feature in the same Android app by optimising the app in all possible ways—network requests, CPU processing, code cleanups, etc.
Our Restaurant app is built using the react-native framework. The restaurant partner manages the order acceptance, preparation, and the status of being ready for pick up by the Delivery Agent (DE) and other modules on the Order Screen.
We have three mechanisms to ensure the Swiggy Consumer orders are shown in real time on the Swiggy Restaurant app.
Although our current order flow works smoothly for day-to-day traffic benchmarks, we have observed some opportunities to improve the performance during controlled local testing with 2x or 3x usual traffic. To scale it to 10x or multi outlets, we need to improve the overall app performance
In early January 2024, after New Year’s Eve, one of our engineers improved the order flow codebase of our Android app. Previously, every order polling fetched all orders from Java Native to React Native via the RN bridge. Now, only updated or new orders are passed, reducing data transfer and processing in React Native, resulting in a significant performance boost.
Our app used to support a single-outlet order flow, but we need to scale it to handle multiple outlet orders simultaneously. To achieve this, we must identify and implement improvements that will allow us to accommodate this expanded functionality efficiently.
The goal was to improve the Order flow performance by reducing in-efficiencies across CPU, network, and RAM useage.
To begin with, we started debugging and improving basic tasks. I did some warm-up pushups 🏃 before tackling this task. Here’s what we did
And the result is …. as unexpected, no change at all 😏
Although all the previous tasks were important and had some impact, they had little to no effect on order flow performance. After a strong black coffee, I went deeper:
This time, I felt proud 😌 These focused tweaks significantly improved the overall app’s hygiene, but the order flow performance remained largely the same 😤.
// ImageKit Resize and crop images feature
const getDeThumbnail = (imageUrl: string): string => {
return imageUrl + "/tr:w-150";
}
In the final attempt, before throwing in the towel, I tried some yoga 🧘 and then set up a complete observation with console logs, network logs, and CPU logs. The problem was finally apparent:
We called the Fetch Orders API on every Firebase Cloud Messaging (FCM) event for each order stage (New, Confirmed, Preparing, Ready, Delivered). This results in 5 API calls per order. During peak hours, if a restaurant has 30 active orders and 10 orders change state, this generates a surge of 10 FCM events per order stage, totalling up to 150 events (30 orders * 5 stages). Each event triggers an immediate Fetch Orders API call, causing performance issues due to the high frequency of calls.
To address this, we implemented API throttle logic to mitigate the performance issues. This logic ensures we do not consecutively make individual API calls for each FCM event. Instead, we group multiple events and make fewer consolidated API calls, reducing the load on the system and improving overall performance. The throttle logic helps manage the API call rate during peak times, ensuring the system remains responsive and efficient.
To fix this bug, we added throttling for FCM events. This cut down on the Fetch Orders API calls, saving processing power on Android devices and reducing server load 🚀
Context cx = getApplicationContext();
int fcmThrottleIntervalInSec = SharedUtils.getFcmBasedPollingInterval(cx); // 10
Long fcmBasedLastTimestamp = SharedUtils.getFcmBasedApiLastTimestamp(cx);
Long timeDifferenceInSec = (Utils.getCurrentTimeStamp() - fcmBasedLastTimestamp) / 1000;
boolean scheduleFetchOrdersApiCall = timeDifferenceInSec > fcmThrottleIntervalInSec;
if (scheduleFetchOrdersApiCall){
SharedUtils.putFcmBasedApiLastTimestamp(cx, Utils.getCurrentTimeStamp());
Log.d(TAG, "about to fetch orders from FCM in: " + fcmThrottleIntervalInSec);
Handler handler = new Handler(Looper.getMainLooper());
handler.postDelayed(new Runnable() {
@Override
public void run() {
final OrderPoll.CallSource source = OrderPoll.CallSource.FCM;
fetchOrdersAPiCall(source);
}
}, fcmThrottleIntervalInSec * 1000);
}
Besides drinking coffee and adding yoga to your routine, follow these rules.
Thanks for your precious time! 🙏
Acknowledgements
This couldn’t have been possible without the help and support of Mitansh Mehrotra, Harpreet Singh, Tarique Javaid and Tushar Tayal.
Huge thanks to the incredible folks at the Vendor team at Swiggy, who tested these changes and made this massive release successful.
Optimising the Swiggy Restaurant app was originally published in Swiggy Bytes — Tech Blog on Medium, where people are continuing the conversation by highlighting and responding to this story.