skip to Main Content

I built an Online Shop Website with Mongo DB and Express.js that has flash sale features on it. Its traffic spiked when it came to the Flash Sale, with 5,000 users per minute.

As we know, in the online shop, every product has available stock. My problem is, for every high-traffic visitor, I encounter minus stock. I think this happens because of race conditions. Some visitors (let’s say 5 visitors) have current stock as 1 left, so the logic tells that they can buy the product, and then when the transaction is successful, the stock is subtracted by 5, which makes the minus data ( 1 – 5 = -4 ). This was my code.

const product = await this.productService.findOne(slug)

if( !product )
  return res.status(404).json({ success: false, message: 'Not Found' })

if( product.available <= 0 )
  return res.json({ success: false, message: 'Out of Stock' })

const newTransaction = await this.transactionService.create(user, product)

await this.productService.update(product.id, { available: product.available - 1 })

return res.json({ success: true, data: newTransaction })

But, then I realised minus data occurred at the field available so Here is my current code.


await sleep(200, 500) // I want to random the sleep time, so I made a function that can random the sleep time with args min, max in milliseconds

const product = await this.productService.findOne(slug)

if( !product )
  return res.status(404).json({ success: false, message: 'Not Found' })

if( product.available <= 0 )
  return res.json({ success: false, message: 'Out of Stock' })

const newTransaction = await this.transactionService.create(user, product)

await this.productService.update(product.id, { available: product.available - 1 })

return res.json({ success: true, data: newTransaction })

My current code temporarily solves my issues. But, when it comes to very high traffic the minus happens again (but not much than the first code, it can reduce the 80-90% minus value) but it still happens.

Currently, I can’t reproduce the bugs, because it will need 2000-5,000 requests hit to my endpoint at the same time to make the minus occur.

Does anyone have any idea what alternative that can I try to solve this issue? Or tools that can reproduce that many requests for one endpoint.

2

Answers


  1. Two things come to my mind:

    1. Include version number in your product and whenerver somebody updates product, ensure it matches the one in database, this should be easily doable i think from the snippet you provided,
    2. Locks, it’s a little bit weird because i see you are making transactions and i think they are using locks under the hood, it would be good for you to read more about concurrency here https://www.mongodb.com/docs/manual/faq/concurrency/

    For reproducing traffic there are a lot of tools like jMeter so pick the one that fits you i guess.

    Login or Signup to reply.
  2. You need to implement a proper locking mechanism to ensure that only one request at a time can modify the stock of a product. One common approach is to use a database transaction to ensure atomicity and consistency of the stock update operation.I strongly recommend you look into Database Concurrency Control and transactions. I will give you a possible solution, but please remember it’s just one of many approaches. Take it as an idea and you will need to find the approach that is most suitable for your use-case. Also I will strongly recommend that you test your solution under simulated high traffic.

    const session = await mongoose.startSession()
    session.startTransaction()
    
    try {
      const product = await this.productService.findOne(slug, { session })
    
      if (!product) {
        session.abortTransaction()
        session.endSession()
        return res.status(404).json({ success: false, message: 'Not Found' })
      }
    
      if (product.available <= 0) {
        session.abortTransaction()
        session.endSession()
        return res.json({ success: false, message: 'Out of Stock' })
      }
    
      const newTransaction = await this.transactionService.create(user, product, { session })
    
      await this.productService.update(product.id, { available: product.available - 1 }, { session })
    
      await session.commitTransaction()
      session.endSession()
    
      return res.json({ success: true, data: newTransaction })
    } catch (error) {
      await session.abortTransaction()
      session.endSession()
      // Handle the error appropriately
    }
    
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search