Skip to content

Instantly share code, notes, and snippets.

@jhaynie
Last active October 24, 2022 22:58
Show Gist options
  • Save jhaynie/485360624a2ae19bd5b3a4903037d8c8 to your computer and use it in GitHub Desktop.
Save jhaynie/485360624a2ae19bd5b3a4903037d8c8 to your computer and use it in GitHub Desktop.
Example prisma retry
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