fix(migration): Ensure that only ONE server can attempt to apply migrations at a time
Resolves a race condition in which non-bundled environments can attempt to run migrations simultaniously. When this occurs, the migrations can break.
1 files changed, 10 insertions, 4 deletions
diff --git a/src/database/Database.ts b/src/database/Database.ts
index 5d1336e7..e16f46ff 100644
--- a/src/database/Database.ts
+++ b/src/database/Database.ts
@@ -47,7 +47,7 @@ if (!process.env.DATABASE && !isHeadlessProcess) {
const dbConnectionString = process.env.DATABASE!;
export const DatabaseType = isHeadlessProcess ? "postgres" : dbConnectionString.split(":")[0]?.replace("+srv", "");
const applyMigrations = process.env.APPLY_DB_MIGRATIONS !== "false";
-
+const MIGRATIONLOCK = 1;
export const DataSourceOptions = isHeadlessProcess
? (undefined as unknown as DataSource)
: new DataSource({
@@ -109,19 +109,25 @@ export async function initDatabase(): Promise<DataSource> {
}
};
if (applyMigrations) {
+ const qr = dbConnection.createQueryRunner();
+ /*
+ The transaction lock ensures that exactly one server is attempting to run migrations at a time.
+ It is session-specific, so should be released if a crash occurs. It is also blocking, so all
+ servers can run their logic.
+ */
+ await qr.query(`Select pg_advisory_lock(${MIGRATIONLOCK})`);
if (!(await dbExists())) {
console.log("[Database] This appears to be a fresh database. Running initial DDL.");
- const qr = dbConnection.createQueryRunner();
const initialPath = path.join(__dirname, "migration", DatabaseType + "-initial.js");
if (fs.existsSync(initialPath)) {
console.log("[Database] Found initial migration file, running it.");
await new (require(`./migration/${DatabaseType}-initial`).initial0)().up(qr);
} else console.log("[Database] No initial migration file found at '", initialPath, "', skipping.");
- await qr.release();
}
-
console.log("[Database] Applying missing migrations, if any.", process.env.APPLY_DB_MIGRATIONS);
await dbConnection.runMigrations();
+ await qr.query(`Select pg_advisory_unlock(${MIGRATIONLOCK})`);
+ await qr.release();
} else {
console.log("[Database] Skipping migrations as per config.");
while (!(await dbExists())) {
|