mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-18 02:21:13 +00:00
feat(core): Add config to override default database ping interval and default idle connection timeout (#15764)
This commit is contained in:
committed by
GitHub
parent
8201202a54
commit
ac06610485
@@ -84,6 +84,10 @@ class PostgresConfig {
|
|||||||
@Env('DB_POSTGRESDB_CONNECTION_TIMEOUT')
|
@Env('DB_POSTGRESDB_CONNECTION_TIMEOUT')
|
||||||
connectionTimeoutMs: number = 20_000;
|
connectionTimeoutMs: number = 20_000;
|
||||||
|
|
||||||
|
/** Postgres idle connection timeout (ms) */
|
||||||
|
@Env('DB_POSTGRESDB_IDLE_CONNECTION_TIMEOUT')
|
||||||
|
idleTimeoutMs: number = 30_000;
|
||||||
|
|
||||||
@Nested
|
@Nested
|
||||||
ssl: PostgresSSLConfig;
|
ssl: PostgresSSLConfig;
|
||||||
}
|
}
|
||||||
@@ -158,6 +162,12 @@ export class DatabaseConfig {
|
|||||||
@Env('DB_TABLE_PREFIX')
|
@Env('DB_TABLE_PREFIX')
|
||||||
tablePrefix: string = '';
|
tablePrefix: string = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The interval in seconds to ping the database to check if the connection is still alive.
|
||||||
|
*/
|
||||||
|
@Env('DB_PING_INTERVAL_SECONDS')
|
||||||
|
pingIntervalSeconds: number = 2;
|
||||||
|
|
||||||
@Nested
|
@Nested
|
||||||
logging: LoggingConfig;
|
logging: LoggingConfig;
|
||||||
|
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ describe('GlobalConfig', () => {
|
|||||||
rejectUnauthorized: true,
|
rejectUnauthorized: true,
|
||||||
},
|
},
|
||||||
user: 'postgres',
|
user: 'postgres',
|
||||||
|
idleTimeoutMs: 30_000,
|
||||||
},
|
},
|
||||||
sqlite: {
|
sqlite: {
|
||||||
database: 'database.sqlite',
|
database: 'database.sqlite',
|
||||||
@@ -72,6 +73,7 @@ describe('GlobalConfig', () => {
|
|||||||
tablePrefix: '',
|
tablePrefix: '',
|
||||||
type: 'sqlite',
|
type: 'sqlite',
|
||||||
isLegacySqlite: true,
|
isLegacySqlite: true,
|
||||||
|
pingIntervalSeconds: 2,
|
||||||
},
|
},
|
||||||
credentials: {
|
credentials: {
|
||||||
defaultName: 'My credentials',
|
defaultName: 'My credentials',
|
||||||
@@ -323,7 +325,9 @@ describe('GlobalConfig', () => {
|
|||||||
process.env = {
|
process.env = {
|
||||||
DB_POSTGRESDB_HOST: 'some-host',
|
DB_POSTGRESDB_HOST: 'some-host',
|
||||||
DB_POSTGRESDB_USER: 'n8n',
|
DB_POSTGRESDB_USER: 'n8n',
|
||||||
|
DB_POSTGRESDB_IDLE_CONNECTION_TIMEOUT: '10000',
|
||||||
DB_TABLE_PREFIX: 'test_',
|
DB_TABLE_PREFIX: 'test_',
|
||||||
|
DB_PING_INTERVAL_SECONDS: '2',
|
||||||
NODES_INCLUDE: '["n8n-nodes-base.hackerNews"]',
|
NODES_INCLUDE: '["n8n-nodes-base.hackerNews"]',
|
||||||
DB_LOGGING_MAX_EXECUTION_TIME: '0',
|
DB_LOGGING_MAX_EXECUTION_TIME: '0',
|
||||||
N8N_METRICS: 'TRUE',
|
N8N_METRICS: 'TRUE',
|
||||||
@@ -339,10 +343,12 @@ describe('GlobalConfig', () => {
|
|||||||
...defaultConfig.database.postgresdb,
|
...defaultConfig.database.postgresdb,
|
||||||
host: 'some-host',
|
host: 'some-host',
|
||||||
user: 'n8n',
|
user: 'n8n',
|
||||||
|
idleTimeoutMs: 10_000,
|
||||||
},
|
},
|
||||||
sqlite: defaultConfig.database.sqlite,
|
sqlite: defaultConfig.database.sqlite,
|
||||||
tablePrefix: 'test_',
|
tablePrefix: 'test_',
|
||||||
type: 'sqlite',
|
type: 'sqlite',
|
||||||
|
pingIntervalSeconds: 2,
|
||||||
},
|
},
|
||||||
endpoints: {
|
endpoints: {
|
||||||
...defaultConfig.endpoints,
|
...defaultConfig.endpoints,
|
||||||
|
|||||||
@@ -102,6 +102,7 @@ describe('DbConnectionOptions', () => {
|
|||||||
key: '',
|
key: '',
|
||||||
rejectUnauthorized: true,
|
rejectUnauthorized: true,
|
||||||
},
|
},
|
||||||
|
idleTimeoutMs: 30000,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -121,6 +122,9 @@ describe('DbConnectionOptions', () => {
|
|||||||
migrations: postgresMigrations,
|
migrations: postgresMigrations,
|
||||||
connectTimeoutMS: 20000,
|
connectTimeoutMS: 20000,
|
||||||
ssl: false,
|
ssl: false,
|
||||||
|
extra: {
|
||||||
|
idleTimeoutMillis: 30000,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import type { DatabaseConfig } from '@n8n/config';
|
||||||
import type { Migration } from '@n8n/db';
|
import type { Migration } from '@n8n/db';
|
||||||
import * as migrationHelper from '@n8n/db';
|
import * as migrationHelper from '@n8n/db';
|
||||||
import { DataSource, type DataSourceOptions } from '@n8n/typeorm';
|
import { DataSource, type DataSourceOptions } from '@n8n/typeorm';
|
||||||
@@ -17,6 +18,7 @@ describe('DbConnection', () => {
|
|||||||
let dbConnection: DbConnection;
|
let dbConnection: DbConnection;
|
||||||
const migrations = [{ name: 'TestMigration1' }, { name: 'TestMigration2' }] as Migration[];
|
const migrations = [{ name: 'TestMigration1' }, { name: 'TestMigration2' }] as Migration[];
|
||||||
const errorReporter = mock<ErrorReporter>();
|
const errorReporter = mock<ErrorReporter>();
|
||||||
|
const databaseConfig = mock<DatabaseConfig>();
|
||||||
const dataSource = mockDeep<DataSource>({ options: { migrations } });
|
const dataSource = mockDeep<DataSource>({ options: { migrations } });
|
||||||
const connectionOptions = mockDeep<DbConnectionOptions>();
|
const connectionOptions = mockDeep<DbConnectionOptions>();
|
||||||
const postgresOptions: DataSourceOptions = {
|
const postgresOptions: DataSourceOptions = {
|
||||||
@@ -35,7 +37,7 @@ describe('DbConnection', () => {
|
|||||||
connectionOptions.getOptions.mockReturnValue(postgresOptions);
|
connectionOptions.getOptions.mockReturnValue(postgresOptions);
|
||||||
(DataSource as jest.Mock) = jest.fn().mockImplementation(() => dataSource);
|
(DataSource as jest.Mock) = jest.fn().mockImplementation(() => dataSource);
|
||||||
|
|
||||||
dbConnection = new DbConnection(errorReporter, connectionOptions);
|
dbConnection = new DbConnection(errorReporter, connectionOptions, databaseConfig);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('init', () => {
|
describe('init', () => {
|
||||||
@@ -174,5 +176,29 @@ describe('DbConnection', () => {
|
|||||||
|
|
||||||
expect(dataSource.query).not.toHaveBeenCalled();
|
expect(dataSource.query).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should execute ping on schedule', async () => {
|
||||||
|
jest.useFakeTimers();
|
||||||
|
try {
|
||||||
|
// ARRANGE
|
||||||
|
dbConnection = new DbConnection(
|
||||||
|
errorReporter,
|
||||||
|
connectionOptions,
|
||||||
|
mock<DatabaseConfig>({
|
||||||
|
pingIntervalSeconds: 1,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const pingSpy = jest.spyOn(dbConnection as any, 'ping');
|
||||||
|
|
||||||
|
// @ts-expect-error private property
|
||||||
|
dbConnection.scheduleNextPing();
|
||||||
|
jest.advanceTimersByTime(1000);
|
||||||
|
|
||||||
|
expect(pingSpy).toHaveBeenCalled();
|
||||||
|
} finally {
|
||||||
|
jest.useRealTimers();
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -131,6 +131,9 @@ export class DbConnectionOptions {
|
|||||||
migrations: postgresMigrations,
|
migrations: postgresMigrations,
|
||||||
connectTimeoutMS: postgresConfig.connectionTimeoutMs,
|
connectTimeoutMS: postgresConfig.connectionTimeoutMs,
|
||||||
ssl,
|
ssl,
|
||||||
|
extra: {
|
||||||
|
idleTimeoutMillis: postgresConfig.idleTimeoutMs,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { inTest } from '@n8n/backend-common';
|
import { inTest } from '@n8n/backend-common';
|
||||||
|
import { DatabaseConfig } from '@n8n/config';
|
||||||
import type { Migration } from '@n8n/db';
|
import type { Migration } from '@n8n/db';
|
||||||
import { wrapMigration } from '@n8n/db';
|
import { wrapMigration } from '@n8n/db';
|
||||||
import { Memoized } from '@n8n/decorators';
|
import { Memoized } from '@n8n/decorators';
|
||||||
@@ -7,6 +8,8 @@ import { DataSource } from '@n8n/typeorm';
|
|||||||
import { ErrorReporter } from 'n8n-core';
|
import { ErrorReporter } from 'n8n-core';
|
||||||
import { DbConnectionTimeoutError, ensureError } from 'n8n-workflow';
|
import { DbConnectionTimeoutError, ensureError } from 'n8n-workflow';
|
||||||
|
|
||||||
|
import { Time } from '@/constants';
|
||||||
|
|
||||||
import { DbConnectionOptions } from './db-connection-options';
|
import { DbConnectionOptions } from './db-connection-options';
|
||||||
|
|
||||||
type ConnectionState = {
|
type ConnectionState = {
|
||||||
@@ -28,6 +31,7 @@ export class DbConnection {
|
|||||||
constructor(
|
constructor(
|
||||||
private readonly errorReporter: ErrorReporter,
|
private readonly errorReporter: ErrorReporter,
|
||||||
private readonly connectionOptions: DbConnectionOptions,
|
private readonly connectionOptions: DbConnectionOptions,
|
||||||
|
private readonly databaseConfig: DatabaseConfig,
|
||||||
) {
|
) {
|
||||||
this.dataSource = new DataSource(this.options);
|
this.dataSource = new DataSource(this.options);
|
||||||
Container.set(DataSource, this.dataSource);
|
Container.set(DataSource, this.dataSource);
|
||||||
@@ -80,9 +84,12 @@ export class DbConnection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Ping DB connection every 2 seconds */
|
/** Ping DB connection every `pingIntervalSeconds` seconds to check if it is still alive. */
|
||||||
private scheduleNextPing() {
|
private scheduleNextPing() {
|
||||||
this.pingTimer = setTimeout(async () => await this.ping(), 2000);
|
this.pingTimer = setTimeout(
|
||||||
|
async () => await this.ping(),
|
||||||
|
this.databaseConfig.pingIntervalSeconds * Time.seconds.toMilliseconds,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async ping() {
|
private async ping() {
|
||||||
|
|||||||
Reference in New Issue
Block a user