mirror of
https://github.com/directus/directus.git
synced 2026-04-25 03:00:53 -04:00
Client-side restart of failed transactions on CockroachDB (#22240)
This commit is contained in:
5
.changeset/bright-mirrors-rush.md
Normal file
5
.changeset/bright-mirrors-rush.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@directus/api': patch
|
||||
---
|
||||
|
||||
Implemented client-side restart of failed transactions for CockroachDB
|
||||
@@ -1,4 +1,6 @@
|
||||
import type { Knex } from 'knex';
|
||||
import { type Knex } from 'knex';
|
||||
import { getDatabaseClient } from '../database/index.js';
|
||||
import { useLogger } from '../logger.js';
|
||||
|
||||
/**
|
||||
* Execute the given handler within the current transaction or a newly created one
|
||||
@@ -7,10 +9,49 @@ import type { Knex } from 'knex';
|
||||
* Can be used to ensure the handler is run within a transaction,
|
||||
* while preventing nested transactions.
|
||||
*/
|
||||
export const transaction = <T = unknown>(knex: Knex, handler: (knex: Knex) => Promise<T>): Promise<T> => {
|
||||
export const transaction = async <T = unknown>(knex: Knex, handler: (knex: Knex) => Promise<T>): Promise<T> => {
|
||||
if (knex.isTransaction) {
|
||||
return handler(knex);
|
||||
} else {
|
||||
return knex.transaction((trx) => handler(trx));
|
||||
try {
|
||||
return await knex.transaction((trx) => handler(trx));
|
||||
} catch (error: any) {
|
||||
const client = getDatabaseClient(knex);
|
||||
|
||||
/**
|
||||
* This error code indicates that the transaction failed due to another
|
||||
* concurrent or recent transaction attempting to write to the same data.
|
||||
* This can usually be solved by restarting the transaction on client-side
|
||||
* after a short delay, so that it is executed against the latest state.
|
||||
*
|
||||
* @link https://www.cockroachlabs.com/docs/stable/transaction-retry-error-reference
|
||||
*/
|
||||
const COCKROACH_RETRY_ERROR_CODE = '40001';
|
||||
|
||||
if (client !== 'cockroachdb' || error?.code !== COCKROACH_RETRY_ERROR_CODE) throw error;
|
||||
|
||||
const MAX_ATTEMPTS = 3;
|
||||
const BASE_DELAY = 100;
|
||||
|
||||
const logger = useLogger();
|
||||
|
||||
for (let attempt = 0; attempt < MAX_ATTEMPTS; ++attempt) {
|
||||
const delay = 2 ** attempt * BASE_DELAY;
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, delay));
|
||||
|
||||
logger.trace(`Restarting failed transaction (attempt ${attempt + 1}/${MAX_ATTEMPTS})`);
|
||||
|
||||
try {
|
||||
return await knex.transaction((trx) => handler(trx));
|
||||
} catch (error: any) {
|
||||
if (error?.code !== COCKROACH_RETRY_ERROR_CODE) throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/** Initial execution + additional attempts */
|
||||
const attempts = 1 + MAX_ATTEMPTS;
|
||||
throw new Error(`Transaction failed after ${attempts} attempts`, { cause: error });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user