Tracking is an integral component of order fulfilment in E-commerce. You can provide a seamless customer experience by building an intuitive order tracking interface. However, under the hood, there’s so much more you need to do. Most importantly, you have to carefully architect your services so as to reduce the unwanted load on your servers.
Considering the rate at which we were growing, we decided to rewrite our Tracking services in a more optimal fashion. In this roundup, I’ll talk about common pitfalls associated with tracking mechanisms and how we came up with a more efficient solution. I’ll also shed some light on our new and optimised tracking microservices.
Tracking in a Nutshell
Before we dive into the technical nuances of tracking, let’s have a bird’ eye view of why it is so important.
Usually, the customer only looks at the larger picture.
“I placed my order five days ago, why hasn’t it reached me yet when it was supposed to be delivered in three days”.
Moreover, customers often tend to forget the expected delivery date for the order.
This is the gap that tracking fills. It keeps customers informed on the status of their orders. Eventually, it helps them build trust in the store.
By providing the relevant information on the whereabouts of orders, tracking also helps to keep up your customer retention metrics. So if you’re witnessing a number of delayed deliveries you could very well be at the risk of losing a lot of customers. In this situation, an optimal tracking service might help you keep those customers intact.
Traditional Tracking Service Explained
Gone were the days when we had to run a query on the entire list of shipments to identify orders that are eligible to be tracked. Though webhooks are accepted as industry standards, it wasn’t an ideal solution for us
Initially, we followed a straightforward and brute-force approach. At that time, it gave us a great time to market and worked well for our product. So let me walk you through how we originally built our tracking services.
When a store processes its order through our panel, we assign it to a courier service provider. The courier service exposes its own APIs for getting real-time updates on the status of the delivery of the order.
Our tracking service runs a cron job to pull data from the courier service’s API periodically. We would then save this information in our database. Our own tracking API would now read this data and send it back to the relevant store’s tracking page.
Drawbacks of the Traditional Approach
In the present scenario, we track over 2.5 million orders every 30 minutes. Moreover, we fetch status updates on more than 1.5 million orders every day. Of course, we weren’t aware of these numbers back then. However, as we started processing more orders on a daily basis, we could see the rapidity with which our numbers would increase.
Therefore, we anticipated that this solution isn’t going to scale well in the future. Largely due to the huge amount of redundant read and write operations we were performing in our database.
Let me reiterate this. For every single request to the courier service API, we executed a write query in our database. We were blindly updating our database records regardless if they had actually changed or not. For instance, let’s say that an order was tracked but it had no status update. Logically, we don’t need to update its entry in our database because, for this entry, nothing has changed.
But our traditional approach didn’t allow us this flexibility. Hence, there was no validation mechanism before updating the tracking data in the database.
Consequently, our own tracking API used by the store was also reading this data from the database. All in all the large number of requests attributed to a large number of external queries. In terms of server resources, tracking became extremely expensive for us and we had to find a more optimal approach.
Introducing the Optimal Tracking Microservice
The first thing we decided to do was pull out our tracking module into a separate microservice. Not because we fancied microservices or it was cool at that time. Simply because it was a wise decision to move forward given our scale. Hence our improved tracking solution would work independently from other applications and will run on a different server.
Like previously, our new tracking microservice would also pull data from the courier service APIs periodically. However, this time instead of writing the entire data in the database, it had a validation mechanism.
The microservice would compare the new data to check if the tracking data has changed. It then pushes the updated data in a queue. This queue provides us an asynchronous call that can reduce unwanted load on the server. We set different queues corresponding to different courier services that we worked with. Consequently, we update our database only for the data that has changed.
Furthermore, we used MySQL partitions to split our database into pools. Each pool was mapped with a frequency of tracking. This frequency represents the interval at which we make a fresh request to get the courier service API, believing that we might get some update on the status of the order. The frequency was decided on the basis of the types of order status.
For instance, pool 0 represents an order that has the status of forward scan. This order would be tracked every 30 minutes. So in this case, pool 0 is mapped to a frequency of 30 minutes. Similarly we had pool 1 to represent orders that had no status updates from the courier services in the last 3 weeks. Orders in this pool would be tracked once every 2 days. Finally, we had pool 3 mapped to frequency of an hour where orders would be tracked on an hourly basis.
Advantages of Optimal Tracking
With the implementation of the optimal tracking microservice, we ensured that all shipments are tracked in real-time. Compared to existing solutions, the concurrency of tracking order status increased. However, this happened without compromising on our CPU resources.
We abstracted away from our main server and introduced a validation mechanism before writing into our database. Thus the CPU utilization of our main application server and database was reduced by a great factor.
To give more perspective, we identified and removed over 4 million redundant update operations to our core database every hour.