Jingles
Jingles

Reputation: 1135

Firestore Transactions is not handling race condition

Objective

User on click a purchase button on the web frontend, it will send a POST request to the backend to create a purchase order. First, it will check the number of available stocks. If available is greater than 0, reduce available by 1 and then create the order.

The setup

Backend (NestJS) queries the Firestore for the latest available value, and reduce available by 1. For debugging, I will return the available value.

  let available;
  try {
    await runTransaction(firestore, async (transaction) => {
      const sfDocRef = doc(collection(firestore, 'items_available'), documentId);
      const sfDoc = await transaction.get(sfDocRef);
      if (!sfDoc.exists()) {
        throw 'Document does not exist!';
      }
      const data = sfDoc.data();
      available = data.available;
      if(available>0){
        transaction.update(sfDocRef, {
          available: available-1,
        });
      }
    });
  } catch (e) {
    console.log('Transaction failed: ', e);
  }
  return { available };

My stress test setup

Our goal is to see all API requests having different available value, this would mean that Firestore Transactions is reducing the value even though there are multiple requests coming in.

I wrote a simple multi-threaded program that queries the backend's create order API, it will query the available value and return the available value. This program will save the available value returned for each API request.

The stress test performed is about 10 transactions per second, as I have 10 concurrent processes querying the backend. Each process will http.get 20 queries:

const http = require('http');

function call(){
  http.get('http://localhost:5000/get_item_available', res => {
    let data = [];
    res.on('data', chunk => {
      data.push(chunk);
    });
    res.on('end', () => {
      console.log('Response: ', Buffer.concat(data).toString());
    });
  }).on('error', err => {
    console.log('Error: ', err.message);
  });
}
for (var i=0; i<20; i++){
  call();
}

The problem

Unfortunately, the available values I got from the requests contains repeated values, that is, having same available values instead of having unique available values.

What is wrong? Isn't Firestore Transactions meant to handle race conditions? Any suggestions on what I could change to handle multiple requests hitting the server and return a new value for each request?

Upvotes: 0

Views: 303

Answers (1)

Frank van Puffelen
Frank van Puffelen

Reputation: 598718

You have a catch clause to handle when the transaction fails, but then still end up returning a value to the caller return { available }. In that situation you should return an error to the caller.

Upvotes: 3

Related Questions