Skip to content

Commit 3af0fb6

Browse files
committed
migrate lock fix2
1 parent e1d177b commit 3af0fb6

1 file changed

Lines changed: 53 additions & 43 deletions

File tree

src/db/migrate.ts

Lines changed: 53 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,68 @@
11
import { migrate } from "drizzle-orm/postgres-js/migrator";
2-
import { db } from "./index.js";
2+
import { drizzle } from "drizzle-orm/postgres-js";
33
import { sql } from "drizzle-orm";
4+
import postgres from "postgres";
45

56
// Advisory lock ID — arbitrary fixed integer used as a mutex across replicas
67
const MIGRATION_LOCK_ID = 72_616_384;
78

9+
const connectionString =
10+
process.env.DATABASE_URL ?? "postgresql://underlay:underlay@localhost:5432/underlay";
11+
812
async function runMigrations(retries = 10, delay = 2000) {
9-
for (let attempt = 1; attempt <= retries; attempt++) {
10-
try {
11-
console.log(`[migrate] Acquiring advisory lock (attempt ${attempt})...`);
12-
13-
// pg_try_advisory_lock is session-level and non-blocking.
14-
// If another replica holds the lock, we wait and retry.
15-
const [lockResult] = await db.execute(
16-
sql`SELECT pg_try_advisory_lock(${MIGRATION_LOCK_ID}) as acquired`,
17-
);
18-
19-
if (!(lockResult as any)?.acquired) {
20-
console.log("[migrate] Another process is running migrations, waiting...");
21-
await new Promise((r) => setTimeout(r, delay));
22-
continue;
23-
}
13+
// Use a single dedicated connection (not a pool) so the advisory lock,
14+
// migration queries, and unlock all run on the same session.
15+
const client = postgres(connectionString, { max: 1 });
16+
const db = drizzle(client);
2417

18+
try {
19+
for (let attempt = 1; attempt <= retries; attempt++) {
2520
try {
26-
console.log("[migrate] Lock acquired. Running migrations...");
27-
await migrate(db, { migrationsFolder: "./src/db/migrations" });
28-
console.log("[migrate] Done.");
29-
} finally {
30-
// Release the advisory lock
31-
await db.execute(sql`SELECT pg_advisory_unlock(${MIGRATION_LOCK_ID})`);
32-
}
21+
console.log(`[migrate] Acquiring advisory lock (attempt ${attempt})...`);
22+
23+
// pg_try_advisory_lock is session-level and non-blocking.
24+
// Returns true/false. Must be on the SAME connection as migrate().
25+
const result = await client`SELECT pg_try_advisory_lock(${MIGRATION_LOCK_ID}) as acquired`;
3326

34-
process.exit(0);
35-
} catch (err: any) {
36-
const isTransient =
37-
err?.cause?.code === "ENOTFOUND" ||
38-
err?.cause?.code === "ECONNREFUSED" ||
39-
err?.cause?.code === "ETIMEDOUT";
40-
if (isTransient && attempt < retries) {
41-
console.log(`[migrate] DB not ready (${err.cause.code}), retrying in ${delay}ms...`);
42-
await new Promise((r) => setTimeout(r, delay));
43-
} else {
44-
throw err;
27+
if (!result[0]?.acquired) {
28+
console.log("[migrate] Another process is running migrations, waiting...");
29+
await new Promise((r) => setTimeout(r, delay));
30+
continue;
31+
}
32+
33+
try {
34+
console.log("[migrate] Lock acquired. Running migrations...");
35+
await migrate(db, { migrationsFolder: "./src/db/migrations" });
36+
console.log("[migrate] Done.");
37+
} finally {
38+
await client`SELECT pg_advisory_unlock(${MIGRATION_LOCK_ID})`;
39+
}
40+
41+
return; // success
42+
} catch (err: any) {
43+
const code = err?.cause?.code ?? err?.code;
44+
const isTransient =
45+
code === "ENOTFOUND" || code === "ECONNREFUSED" || code === "ETIMEDOUT";
46+
if (isTransient && attempt < retries) {
47+
console.log(`[migrate] DB not ready (${code}), retrying in ${delay}ms...`);
48+
await new Promise((r) => setTimeout(r, delay));
49+
} else {
50+
throw err;
51+
}
4552
}
4653
}
47-
}
4854

49-
// If we exhausted retries waiting for the lock, exit successfully —
50-
// the other replica already ran migrations.
51-
console.log("[migrate] Lock not acquired after retries — another replica handled it.");
52-
process.exit(0);
55+
// If we exhausted retries waiting for the lock, exit successfully —
56+
// the other replica already ran migrations.
57+
console.log("[migrate] Lock not acquired after retries — another replica handled it.");
58+
} finally {
59+
await client.end();
60+
}
5361
}
5462

55-
runMigrations().catch((err) => {
56-
console.error("[migrate] Failed:", err);
57-
process.exit(1);
58-
});
63+
runMigrations()
64+
.then(() => process.exit(0))
65+
.catch((err) => {
66+
console.error("[migrate] Failed:", err);
67+
process.exit(1);
68+
});

0 commit comments

Comments
 (0)