Schema definitions Users collection: (optimisticConcurrency: true)
{
_id: [Object Id],
pingedAt: [Date],
currentBal: [Int32],
{ ...otherUserAttibutes }
}
Bookings collection (no optimisticConcurrency imposed on this collection)
{
_id: [Object Id],
user: [Ref to User -> the user for whom the booking was made],
status: [0, 1, 2, 3, ...]
{ ...otherBookingAttributes }
}
All api calls to the backend have an authenticate() middleware to cross, which authenticates by querying for the user after validating the token in the auth header. Then retrieves the user object and ends up updating the following attribute
pingedAt → current Date
Scenario causing the issue:
- At 12:01:01:100 ms /cancel-booking/:bookingId - PUT call was initiated and a mongoose session is started.
const session = await mongoose.startSession();
session.startTransaction();
const booking = await this.bookingsRepository.getBookingWithSession({ _id: bookingId }, session)
- At 12:01:01:200 ms user makes /bookings - GET call and at 12:01:01:250 ms the user’s pingedAt is updated during authentication.
user.pingedAt = new Date();
await user.save();
- By 12:01:01:300 ms, the booking that was retrieved with session is changed to CANCELLED
booking.status = 3
await booking.save({ session })
- In the same cancel booking call, we add money to user’s wallet. Say booking fee is 300/-. At 12:01:01:350 ms we save user object with updated balance.
booking.user.currentBal = booking.user.currentBal + 300
await booking.user.save({ session });
WriteConflict error is thrown as the same user object has already changed at 12:01:01:250 ms
Error logs
MongoError: WriteConflict
at MessageStream.messageHandler (/home/ubuntu/api.backend.js/node_modules/mongodb/lib/cmap/connection.js:268:20)
at MessageStream.emit (events.js:315:20)
at MessageStream.EventEmitter.emit (domain.js:483:12)
at processIncomingData (/home/ubuntu/api.backend.js/node_modules/mongodb/lib/cmap/message_stream.js:144:12)
MessageStream._write (/home/ubuntu/api.backend.js/node_modules/mongodb/lib/cmap/message_stream.js:42:5)
at doWrite (_stream_writable.js:403:12)
writeOrBuffer (_stream_writable.js:387:5)
at MessageStream.Writable.write (_stream_writable.js:318:11)
at TLSSocket.ondata (_stream_readable.js:716:22)
at TLSSocket.emit (events.js:315:20)
at TLSSocket.EventEmitter.emit (domain.js:483:12)
at addChunk (_stream_readable.js:295:12)
at readableAddChunk (_stream_readable.js:271:9)
at TLSSocket.Readable.push (_stream_readable.js:212:10)
at TLSWrap.onStreamRead (internal/stream_base_commons.js:186:23)
at TLSWrap.callbackTrampoline (internal/async_hooks.js:120:14)}