fix(migrations): rebuild an index a failed concurrent build left invalid

A CREATE INDEX CONCURRENTLY that fails half-way leaves an INVALID index,
and the migration re-run after migrate resolve --rolled-back finds it
through IF NOT EXISTS and does nothing: recorded applied, with an index
the planner never uses. 0477 has this shape.

The boot now drops an INVALID index that a not-yet-applied migration
builds concurrently before it runs migrate deploy, with a bounded lock
wait, so the re-run builds it again. An INVALID index whose migration is
recorded applied is named on stderr with the rebuild that mends it. The
deploy runbook says both.
This commit is contained in:
Marc-André Bombeck committed 2026-10-25 02:42:09 +01:00
1 parent b2c99e5c8d
commit 4390614463
4 files changed
+380 -1

No files matched your search

+47
View File
@@ -22,7 +22,9 @@ import { join } from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import {
concurrentIndexesIn,
decideLockRetry,
invalidIndexPlan,
lockTimeoutInReport,
prismaEnv,
retryDelays,
@@ -386,3 +388,48 @@ process.exit(0);
expect(result.stderr).toContain("not marked safe to re-run");
});
});
describe("the invalid indexes a failed concurrent build leaves", () => {
it("reads the index each concurrent migration builds", () => {
const sql = readFileSync(
join(
process.cwd(),
"prisma",
"migrations",
"0477_measurements_ingest_token_index",
"migration.sql",
),
"utf8",
);
expect(concurrentIndexesIn(sql)).toEqual([
"measurements_ingest_token_id_id_idx",
]);
expect(
concurrentIndexesIn(
'-- CREATE INDEX CONCURRENTLY IF NOT EXISTS "commented"\nCREATE UNIQUE INDEX CONCURRENTLY IF NOT EXISTS "u" ON "t" ("c");',
),
).toEqual(["u"]);
expect(concurrentIndexesIn('CREATE INDEX "plain" ON "t" ("c");')).toEqual(
[],
);
});
it("drops only an index whose building migration has not been applied", () => {
const builtBy = new Map([
["pending_idx", "0477_pending"],
["applied_idx", "0300_applied"],
]);
const plan = invalidIndexPlan({
invalid: ["pending_idx", "applied_idx", "stray_idx"],
builtBy,
applied: new Set(["0300_applied"]),
});
expect(plan.drop).toEqual([
{ index: "pending_idx", migration: "0477_pending" },
]);
expect(plan.report).toEqual([
{ index: "applied_idx", migration: "0300_applied" },
{ index: "stray_idx", migration: null },
]);
});
});