Run migrations in a multy tenancy environment #401
-
Hey guys, I got stuck trying to adapt an automatic migration process at runtime in a multi-tenancy environment. I have been digging through the issues but it seems there is nothing similar to what I want to achieve. Any help would be greatly appreciated. Scenariosequelize: 6.3.5 For each tenant created at runtime, I run a migration process, however, it does not allow to run multiple times the same migration because of expected reasons (on the first migration it creates a new record with the migration name on the table "SequelizeMeta" in the "public" schema). Based on that, I tried two different approaches: 1st approachBy passing
The problem with this approach is that the second time I call to 2nd approachIn this approach, I went a bit further playing around with the sequelize object but with no success. Basically, I tried to instantiate a new Umzug instance fo each time I wanted to run the migration process but also didn't work. This still creates the "SequelizeMeta" table in "public"
Any help is really appreciated. Thanks. |
Beta Was this translation helpful? Give feedback.
Replies: 11 comments 6 replies
-
Your 2nd approach above looks sensible to me. What value does Side note - make sure you |
Beta Was this translation helpful? Give feedback.
-
Hi @mmkal, Thanks for your response! In the second approach, Now that umzug is working as expected I got into a new situation. So, basically, since I'm calling
But what happened is that it throws the next error
And with that, the
I'm not aware at the moment of the possible side effects of this but since I haven't found any mechanism provided by sequelize to change the schema other than in the query functions itself so I came up with that solution. However, nothing of this still worked, for some reason umzug runs the migration the first time on the first tenant properly but then with the second tenant, Umzug checks the migration table on the first tenant schema :
By the way, I tried without hardcoding the schema into the sequelize connection option payload and using global variables for testing purposes and it's happening the same. What do you think? Thanks, |
Beta Was this translation helpful? Give feedback.
-
Hi - will be able to take a closer look later this week, but this should absolutely be possible. You can pass anything you like to context, passing the queryInterface object is just an optional convention: async migrate(schemaName) {
const umzug = new Umzug({
migrations: { glob: 'migrations/*.js' },
context: {
queryInterface: this.connection.getQueryInterface(),
schema: schemaName,
},
storage: new SequelizeStorage({
sequelize: connection,
modelName: 'migrations_meta',
schema: schemaName,
}),
logger: databaseLogger.logger,
});
await umzug.up();
connection.options.schema = undefined;
} Then in your migration(s), change how you use context slightly: // roles-migrations.js
async function up({ context: { queryInterface, schema } }) {
await queryInterface.bulkInsert({ tableName: 'roles', schema }, [
{
id: 'admins',
created_at: new Date(),
updated_at: new Date(),
},
{
id: 'managers',
created_at: new Date(),
updated_at: new Date(),
},
{
id: 'members',
created_at: new Date(),
updated_at: new Date(),
},
]);
}
async function down({ context: { queryInterface, schema } }) {
await queryInterface.bulkDelete({ tableName: 'roles', schema }, null, {});
}
module.exports = { up, down }; |
Beta Was this translation helpful? Give feedback.
-
Hi @mmkal, Oh, thanks for pointing that out! For some reason I thought that was not possible, I did the changes that you mentioned but still does not work as expected. Basically happens what I described in my previous post. Thanks for taking the time to take a look at this. Really appreciated. |
Beta Was this translation helpful? Give feedback.
-
Hi again, I have been getting deeper on the It seems there is a function From sequelize.js:
This is totally fine when there is no schema-based approach like in a multi-tenant architecture, hence Sequelize also support other db engines we can check if a model has been defined by checking the Correct me if I'm wrong but I think the function Hope all this helps. Thanks PS: I will be happy to make a PR if you agree with the solution I described above. I'm also open to any other approach you think could be better. |
Beta Was this translation helpful? Give feedback.
-
Sequelize storage is supposed to be multi-dialect so not really workable to put postgres-specific sql in there. You might not need to though. AFAIK each sequelize instance has its own model manager, so likely your problem is coming from using a shared sequelize instance. It's hard to tell for sure from your sample code, but it looks like async function runMigration(schema) {
+ const sequelize = new Sequelize (..., { schema });
const umzug = new Umzug({
migrations: { glob: 'migrations/*.js' },
context: sequelize.getQueryInterface(),
storage: new SequelizeStorage({
sequelize,
schema
}),
});
await umzug.up();
} See sequelize constructor docs for how to pass it in. In fact, assuming that works, by passing the schema into the sequelize constructor, you likely wouldn't need to inject it into the context anymore - I expect it'd be the default schema for the queryInterface object. |
Beta Was this translation helpful? Give feedback.
-
Hey @mmkal, That's right, it's a shared instance that is initialized on booting. I address the queries by using the schema parameter like Don't you think that creating a sequlize instance per tenant is an unnecessary workload taking in considerations the multiple connections are created to the db and the memory that having all the models definitions per tenant migth consume. I'm wondering if there is a way of doing it just by using a shared instance, but maybe that's actually not the ideal way of using sequlize. If you don't mind it will be helpfully to read your thoughts about it. Thanks. |
Beta Was this translation helpful? Give feedback.
-
I wasn't aware of Sequelize docs do recommend using a shared instance - but in this case you may be ok with using multiple, depending on how long they're used for, how pooling is configured, and how many schemas you need to migrate. Usually migrations are run separately from application code, so hopefully each instance could tear itself down fairly quickly. Definitely worth testing out though. If you can find a way to explicitly pass the schema to the various calls in SequelizeStorage, a PR would be welcome. I'm planning on adding an examples directory soon, and it'd be good to be able to include this use-case. Note: To be honest I don't currently use sequelize so I'm not the right person to comment on best practices. I also maintain a postgres-only migrator which uses umzug with slonik: https://npmjs.com/package/@slonik/migrator. I prefer writing raw sql over ORMs generally. @papb may be able to shed some light/CC some sequelize other experts. |
Beta Was this translation helpful? Give feedback.
-
Hey @mmkal, I think that's a good point, I can just create the instance for the migration process and close the connection using In another hand, I'm still interested in getting my hands on the SequelizeStorage in order to make it works just using a sequelise shared instance, so I'll take some time this week to work on it so hopefully, I will be able to send a PR for evaluation. It would be great if other people can give their thoughts about it but it's up to you to keep this thread open as long as you want, I'm very grateful for the information given so to me it's crystal clear ;). PS: This sounds interesting , I will take a look to slonik, thanks for sharing! |
Beta Was this translation helpful? Give feedback.
-
Hey @mmkal, Your response came at the perfect timing, I was playing around with the
This way allows having migrations in runtime in a multi tenant environment without any problem. Thanks for all your help and hope this helps other to deal with this kind of scenario. |
Beta Was this translation helpful? Give feedback.
-
Hello, i'm having the same issue. Does someone know a way to fix this and keep using the table SequelizeMeta? |
Beta Was this translation helpful? Give feedback.
Hey @mmkal,
Your response came at the perfect timing, I was playing around with the
SequelizeStorage
in order to have PR candidate that cover the use case we have been discussing and I realized a very crucial thing. TheSequelizeStorage
constructors allow passing the model object itself! That changes all. I can simply define the "SequelizeMeta" model for each tenant on runtime and pass it to theSequelizeStorage
constructor. It worked like a charm, this is the code example: