-
Notifications
You must be signed in to change notification settings - Fork 15
/
script.ts
99 lines (89 loc) · 2.78 KB
/
script.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
import { backOff, IBackOffOptions } from "exponential-backoff";
import { Prisma, PrismaClient } from "@prisma/client";
function RetryTransactions(options?: Partial<IBackOffOptions>) {
return Prisma.defineExtension((prisma) =>
prisma.$extends({
client: {
$transaction(...args: any) {
return backOff(() => prisma.$transaction.apply(prisma, args), {
retry: (e) => {
// Retry the transaction only if the error was due to a write conflict or deadlock
// See: https://www.prisma.io/docs/reference/api-reference/error-reference#p2034
return e.code === "P2034";
},
...options,
});
},
} as { $transaction: typeof prisma["$transaction"] },
})
);
}
const prisma = new PrismaClient().$extends(
RetryTransactions({
jitter: "full",
numOfAttempts: 5,
})
);
async function main() {
const before = await prisma.user.findFirstOrThrow();
console.log("Before: ", before);
await Promise.allSettled([
firstTransaction(before.id),
secondTransaction(before.id),
]);
const after = await prisma.user.findUniqueOrThrow({
where: { id: before.id },
});
console.log("After: ", after);
}
// Runs a read-modify-write transaction, where the "modify" step takes 2000ms,
// enough time for the second transaction to cause a serialization failure
async function firstTransaction(id: string) {
await prisma.$transaction(
async (tx) => {
console.log("First transaction started");
try {
// Read
const user = await tx.user.findUniqueOrThrow({ where: { id } });
// "Modify"
await sleep(2000);
// Write
await tx.user.update({ where: { id }, data: { firstName: "Albert" } });
} catch (e) {
console.log("First transaction failed");
throw e;
}
},
{ isolationLevel: Prisma.TransactionIsolationLevel.Serializable }
);
console.log("First transaction committed");
}
// Waits 1000ms, then writes to the database, causing a conflict with the first transaction
async function secondTransaction(id: string) {
await sleep(1000);
await prisma.$transaction(
async (tx) => {
console.log("Second transaction started");
try {
await tx.user.update({ where: { id }, data: { firstName: "Beto" } });
} catch (e) {
console.log("Second transaction failed");
throw e;
}
},
{ isolationLevel: Prisma.TransactionIsolationLevel.Serializable }
);
console.log("Second transaction committed");
}
function sleep(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
main()
.then(async () => {
await prisma.$disconnect();
})
.catch(async (e) => {
console.error(e);
await prisma.$disconnect();
process.exit(1);
});