How To Build Flash Sale App with GoLang and Couchbase

This is how I describe flash sale. It’s like when you are very hungry, you decide to go to a restaurant and you see all your favorite dish that you want to eat on the menu. And then one day, there is a big promo for your favorite dish at some restaurants, there is a discount within a very limited time. What are you gonna do?
Limited stock or the effort people have to give in obtaining items makes them feel that it relates to their pride, especially when they get the items. People rush whenever there is a flash sale at a store, both offline and online, where millions of users throw themselves into the flash sale.
It is a big challenge for software engineers to build a reliable software for flash sale because there are so many factors to consider. But I will not go too deep on how to build flash sale app end-to-end.
We all know the that hottest language programming right now is GoLang. In short, GoLang is easy to read or write yet so fast, statically typed, has huge open source community, powerful on concurrency and also easy to scale. Even though GoLang is a pragmatic or opinionated language in which everyone can have their own style in writing program with it, it will tackle most of the challenge that we have in today’s modern tech stack as long as we have clean codes.
As our front and the middle layer of our apps is fast we need a persistent layer that has fast performance too. Yup, I found that Couchbase and GoLang swipe right each other on Tinder. The absence of NoSQL Document Oriented to store data in Couchbase makes most of software engineers think that it is not suitable for storing transaction data though. Still, it is worth to try!
For example, we are now trying to build a FlashSale() function on backend flash sale apps where every time the function is called, it will decrement the available stock quantity of an item on the warehouse. Here is a simple architecture diagram, and early implementation:
func FlashSale(reqData Request) (error) {
itemObj, err := cb.FindItemByKey(reqData.itemKey)
if err != nil {
return err
}
if itemObj.AvailableQty < 1 {
return exc.NewNotEnoughQtyException()
}
itemObj.AvailableQty -= reqData.Qty
err = cb.Upsert(&itemObj)
if err != nil {
return err
}
return nil
}Problem
The code above looks like it’s working and good enough, but as we have multi-cluster of service that runs FlashSale() function, it will cause data integrity issue. As we expect that our apps are a flash sale app, the chaos will happen. Data race condition will occur if multi-user hit our function at the same time. It can mess things up, resulting in wrong calculation and negative amount of stock of items, for example. In other word, a disaster!
Implementation
To prevent data race condition, Couchbase has CAS features that will not allow us to update data if current CAS data is changed. CAS is random uint32 in GoLang and will change every time a document on couchbase is changed. Also, we are not using Upsert() method anymore. Here is final FlashSale() function to prevent data race condition occurs:
func FlashSale(reqData Request) (error) {
for true {
itemObj, CAS, err := cb.FindItemByKey(reqData.itemKey)
if err != nil {
return err
}
if itemObj.AvailableQty < 1 {
return exc.NewNotEnoughQtyException()
}
itemObj.AvailableQty -= reqData.Qty
err = cb.Replace(&itemObj, CAS)
switch err.(type) {
case *exc.CasNotMatched:
continue
default:
return err
}
break
}
return nil
}Voilà~~ Now our function will never break our stock data on production. Performance? Leave it to GoLang and Couchbase. With 2 node clusters and low standard Google Cloud Platform template server, FlashSale() function have around 1000–1150 Throughput Per Second. Anyway, the code above is only a snippet, not a full code because it will be too long for this post. The full code can be found here.
Enjoy!
Article was written by Hifnie Bilfash. You can also check his other articles here.
Are you curious about how we contribute to society with the help of technology? Send your latest CV to recruitment@ruma.co.id and let’s see what we can do together!
