Skip to content
Advertisement

Node js shared variables and multiple users and multiple entry-points

I have an node-js application that I’m switching from a single-tenant database to a multi-tenant database. The application code is called from an express api but there are also services that run through a different entrypoints, so req.session is not always available.

Currently I have database function calls all throughout the app like:

database.select.users.findByUserId(123, callback)

Since the app is changing to multi-tenant database, I need to be able to send the postgreSQL schemaName to the database functions. I know I can edit the signature of every database call to this:

database.select.users.findByUserId(schemaName, 123, callback)

But it’s very labor intensive, broad sweeping, and is going to create a lot of bugs. I’m hoping to find a safe way to pass the postgres schemaName to the database wrapper, without having a race condition of some kind where this “global” schemaName variable is somehow overwritten by another caller, thus sending the wrong data.

Here’s some psuedo-code of what I’m considering writing, but I’m worried it wont be “thread-safe” once we deploy.

// as early as possible in the app call:

database.session.initSchema('schema123');

//session.js

let schema = null;

module.exports.initSchema = function (s) {
    schema = s;
};

module.exports.getSchema = function () {
    return schema;
};


// before I hit the database, i would call getSchema() and pass it to postgreSQL

This approach works, but what if Caller2 calls initForSchema() with different values while Caller1 hasn’t finished executing? How can I distinguish which caller is asking for the data when using one variable like this? Is there any way for me to solve this problem safely without editing the signature of every database function call? Thanks for the advice.

edit

I’m leaning towards this solution:

database.session.initSchema('schema123');
//then immediately call
database.select.users.findByUserId(123, callback);

The advantage here is that nothing asynchonous happens between the two calls, which should nullify the race condition possibility, while keeping the original findByUserId signature.

Advertisement

Answer

I don’t think doing what you’re thinking will work because I don’t see a way you’re going to get around those race conditions. If you do:

app.use((request, response, next) => {
  // initialize database schema

  next()
})

It would be ideal because then you can do it only once across all routes, but another request might hit the server a millisecond later and it changes the schema again.

Alternatively you can do that in each separate route, which would work, but then you’re doing just as much work as just doing it in the database call in the first place. If you have to reinitialize the schema in each route then it’s the same thing as just doing it in the call itself.

I was thinking for a while about a solution and then best I can come up with is whether or not you can do it in the connection pool itself. I have no idea what package you’re using or how it’s creating DB connections. But something like this:

const database = connection.getInstance('schemaExample')

// continue to do database things here

Just to show an example of what I’m thinking. That way you can create multiple connection pools for the different schemas on startup and you can just query on the one with the correct schema avoiding all the race conditions.

The idea being that even if another request comes in now and uses a different schema, it will be executing on a different database connection.

User contributions licensed under: CC BY-SA
9 People found this is helpful
Advertisement