Skip to content

Commit

Permalink
retry db migrations
Browse files Browse the repository at this point in the history
In case multiple instances of server are trying to run migration at
startup, knex lock mechanism can lead to one of the instance migration
to fail and therefore fail to deploy.
In order to avoid failed migration because of lock, we wait and retry
migrations
  • Loading branch information
TBonnin committed Mar 4, 2024
1 parent 84522ab commit 1327b82
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 1 deletion.
6 changes: 5 additions & 1 deletion packages/shared/lib/db/database.ts
@@ -1,5 +1,6 @@
import knex from 'knex';
import type { Knex } from 'knex';
import { retry } from '../utils/retry.js';

export function getDbConfig({ timeoutMs }: { timeoutMs: number }): Knex.Config<any> {
return {
Expand Down Expand Up @@ -30,7 +31,10 @@ export class KnexDatabase {
}

async migrate(directory: string): Promise<any> {
return this.knex.migrate.latest({ directory: directory, tableName: '_nango_auth_migrations', schemaName: this.schema() });
return retry(async () => await this.knex.migrate.latest({ directory: directory, tableName: '_nango_auth_migrations', schemaName: this.schema() }), {
maxAttempts: 4,
delayMs: (attempt) => 500 * attempt
});
}

schema() {
Expand Down
21 changes: 21 additions & 0 deletions packages/shared/lib/utils/retry.ts
@@ -0,0 +1,21 @@
interface RetryConfig {
maxAttempts: number;
delayMs: number | ((attempt: number) => number);
}

export async function retry<T>(fn: () => T, config: RetryConfig): Promise<T> {
const { maxAttempts, delayMs } = config;
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
return fn();
} catch (error) {
if (attempt < maxAttempts) {
const delay = typeof delayMs === 'number' ? delayMs : delayMs(attempt);
await new Promise((resolve) => setTimeout(resolve, delay));
} else {
throw error;
}
}
}
throw new Error('unreachable');
}
40 changes: 40 additions & 0 deletions packages/shared/lib/utils/retry.unit.test.ts
@@ -0,0 +1,40 @@
import { expect, describe, it } from 'vitest';
import { retry } from './retry';

describe('retry', () => {
it('should retry', async () => {
let count = 0;
const result = await retry(
() => {
count++;
if (count < 3) {
throw new Error('my error');
}
return count;
},
{
maxAttempts: 3,
delayMs: () => 0
}
);
expect(result).toEqual(3);
});

it('should throw error after max attempts', async () => {
let count = 0;
try {
await retry(
() => {
count++;
throw new Error('my error');
},
{
maxAttempts: 3,
delayMs: () => 0
}
);
} catch (error: any) {
expect(error.message).toEqual('my error');
}
});
});

0 comments on commit 1327b82

Please sign in to comment.