Search code examples
node.jsmongodbparallel-processingnestjshttp-post

NodeJS (Nest JS) parallel requests create duplicate data and skip all checks for duplicity


In Nodejs (nestjs) there is a controller which handles incoming requests and calls the service function for the updation of the record. But sometimes the request is called for multiple times for the same data which results in the duplicity of the data.

The database used is MongoDB

// schema
// online payment

{
 _id: ObjectId(),
 txnId: string;
 status: "success/ pending / failed",
 response: {},
 invoiceNumber: string,
}

//payment

{
_id: ObjectId(),
invoiceNumber:string;
onlinePaymentId: string;
... other params

}

Let's say the data is created once. When the update request (success /failure) hits the server it hits multiple times for the same data.

In service, I have added the functionality for updating the data status -> success / failed

When the status is a success, it results in creation on payment document. And update the invoiceNumber in onlinePaymentDocument.

I have added checks i.e. if the invoiceNumber exist in the online payment it will not create payment again.

But still, the payment document is created for the same onlinePayment document.

-- Next thing I tried is a single execution of the updateOnlinePayment function.


class PaymentService {
 private lock = false;

// update payment
async update(data){

// all check for duplicity (invoice number already exist in document)
// find payment
// is invoice number exist
// if exist do nothing 


 if(this.lock){
   // delay 1000 ms
   return this.update(data) 
 }

 this.lock = true;

 // update online payment and create payment
// update invoice number in payment.

this.lock = false
return "online payment"
}
}

But still, the rate is reduced but not removed completely. Am I doing anything wrong? Is there any other method I can use to remove this type of parallel execution? or Any solution for this type of logic.


Solution

  • You need to run several operations atomically, that is:

    • check if invoice number exists
    • find payment
    • update or create couple of documents

    There can be race conditions at each of these steps, and classical solution is database transaction.
    Transaction allows running series of operations and if only storing to db if all goes well.
    If not, all the changes are rolled back.

    This is one of the ways use MongoDB transactions in nest.js:

    @Injectable()
    class PaymentService {
      constructor(
        // @InjectModel ...
        @InjectConnection() private readonly connection: mongoose.Connection,
      ) {}
    
      async update(data) {
        const session = await this.connection.startSession();
    
        session.startTransaction();
        try {
          // run all your business logic here
          // that is check for invoice number
          // find payment
          // and update documents
        } catch (error) {
          await session.abortTransaction();
          throw error;
        } finally {
          session.endSession();
        }
      }