2

How can one ensure that the database calls are asynchronous for concurrent API calls?

For example, I have this model that represents a pool.

{
    limit: number e.g 5
    isOpen: boolean // will close when users.length == limit
    users: []
}

I want that at any given time, there's only 1 non-full pool in the DB. So I have something like this that runs on an API call.

let openPool = model.findOne({isOpen: true});
if(!openPool){

    //create a new pool
    model.insert({
        limit: 5,
        users: [currentuser]
        isFull: false
    });
}else{

    //add the current user to existing open pool
    model.update({
        users: {$push: {currentuser}}
    });

    //close pool if full
    if(model.users.length == model.users.limit){
        model.update({
            isOpen: {$set: false}
        });
    }

}

When I send 10 requests at the same time to this endpoint, each of them thinks they are the 1st and then create a new pool. So I end up with new 10 pools where I expected 2 full (closed) pools of 5 users each.

Is there a way to ensure that all these requests execute sequentially?

Maybe sequentially is a bad idea. But overall, I'm looking for a way to keep the state of the DB always be valid.

Is Nodejs good for this?

Any pointers to the right direction will be much appreciated.

1
  • My 2 cents: maybe you can hack it by storing your pools in Node's scope, but then your code can't be distributed easily (you'd end up with the same issue if you deploy on a cluster). For the sequential read/write, I don't think Mongo allows locks on read, and eventually you'd have the same issue if you scale your DB. Depending on the ends you need this system for, you may be able to handle pool consistency afterwards, by triggering a script (polling?) that would check if there are several pools open and merge them if the case arises. Commented Jun 4, 2019 at 4:23

1 Answer 1

1

The solution was to use a mutex to synchronize the critical block.
I used async-lock

var AsyncLock = require('async-lock');
var lock = new AsyncLock();

requestHandler(){
    return new Promise(async (resolve,reject) => {
        lock.acquire('key', async () => {
           let openPool = await model.findOne({isOpen: true});
           if(!openPool){

           //create a new pool
           await model.insert({
               limit: 5,
               users: [currentuser]
               isFull: false
           });
        }else{
            //add the current user to existing open pool
            await model.update({
                users: {$push: {currentuser}}
            });

            //close pool if full
            if(model.users.length == model.users.limit){
                model.update({
                    isOpen: {$set: false}
                });
            }
        }

        return resolve(true);
    }).then(() => {
        //lock released
    });
}          
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.