Last active
October 24, 2022 22:58
-
-
Save jhaynie/485360624a2ae19bd5b3a4903037d8c8 to your computer and use it in GitHub Desktop.
Example prisma retry
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Prisma, PrismaClient } from '@prisma/client'; | |
const sleep = (val: number) => new Promise((resolve) => setTimeout(resolve, val)); | |
const maxRetryCount = 5; | |
const retryableErrorCodes = ['P1001', 'P2028', 'P2010', 'P2034', '40001']; | |
const isRetryable = (code: string) => retryableErrorCodes.includes(code); | |
const backoffMs = 100; | |
type TxCallback<R> = ( | |
fn: (prisma: Prisma.TransactionClient) => Promise<R>, | |
options?: { maxWait?: number; timeout?: number; isolationLevel?: Prisma.TransactionIsolationLevel }, | |
) => Promise<any>; | |
const runTransaction = async (tx: any, args: any, options: any, debug = false): Promise<any> => { | |
if (typeof args === 'function') { | |
const txFn = tx as TxCallback<any>; | |
return txFn(async (tx) => { | |
for (let c = 0; c < maxRetryCount; c++) { | |
try { | |
const started = Date.now(); | |
await tx.$executeRawUnsafe('SAVEPOINT cockroach_restart'); | |
const res = await args(tx); | |
if (debug) { | |
console.debug('executed SQL took: %dms in %d attempts (res=%o)', Date.now() - started, 1 + c, res); | |
} | |
await tx.$executeRawUnsafe('RELEASE SAVEPOINT cockroach_restart'); | |
return res; | |
} catch (ex: any) { | |
if (debug) { | |
console.debug('cockroachdb: transaction encountered an error: %s (%s)', ex.message, ex.code); | |
} | |
if (!isRetryable(ex.code)) { | |
throw ex; | |
} | |
if (ex.meta?.code === '0A000') { | |
throw ex; | |
} | |
await tx.$executeRawUnsafe('ROLLBACK TO SAVEPOINT'); | |
console.error('going to sleep and try and retry failed tx', 1 + c, ex); | |
await sleep(Math.pow(2, c) * backoffMs); | |
} | |
} | |
throw new SQLRetryTimeoutException(args); | |
}); | |
} else { | |
// for passing in an array, for now, we just execute it ... might be able to change this in the future | |
return tx(args, options); | |
} | |
}; | |
export const createPrismaClient = async (optionsArg?: any) => { | |
const prisma = new PrismaClient(optionsArg); | |
useMiddleware(prisma); | |
await prisma.$connect(); | |
const oldTx = prisma.$transaction.bind(prisma); | |
const debug = !!optionsArg?.log?.includes('query'); | |
prisma.$transaction = async (args: any, options: any) => runTransaction(oldTx, args, options, debug); | |
return prisma; | |
}; | |
export class SQLRetryTimeoutException extends Error { | |
public params: any; | |
constructor(params: any) { | |
super('SQL Retry Timeout Exception'); | |
this.params = params; | |
} | |
} | |
const useMiddleware = (prisma: PrismaClient) => { | |
// add middleware | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment