Skip to content

Commit b99d6a5

Browse files
Add config to override default database ping interval and default idle connection timeout of postgres database (to release a connection from the pool)
1 parent 8ebfe79 commit b99d6a5

File tree

5 files changed

+53
-3
lines changed

5 files changed

+53
-3
lines changed

packages/@n8n/config/src/configs/database.config.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,10 @@ class PostgresConfig {
8484
@Env('DB_POSTGRESDB_CONNECTION_TIMEOUT')
8585
connectionTimeoutMs: number = 20_000;
8686

87+
/** Postgres idle connection timeout (ms) */
88+
@Env('DB_POSTGRESDB_IDLE_CONNECTION_TIMEOUT')
89+
idleTimeoutMs: number = 30_000;
90+
8791
@Nested
8892
ssl: PostgresSSLConfig;
8993
}
@@ -158,6 +162,12 @@ export class DatabaseConfig {
158162
@Env('DB_TABLE_PREFIX')
159163
tablePrefix: string = '';
160164

165+
/**
166+
* The interval in seconds to ping the database to check if the connection is still alive.
167+
*/
168+
@Env('DB_PING_INTERVAL_SECONDS')
169+
pingIntervalSeconds: number = 2;
170+
161171
@Nested
162172
logging: LoggingConfig;
163173

packages/cli/src/databases/__tests__/db-connection-options.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ describe('DbConnectionOptions', () => {
102102
key: '',
103103
rejectUnauthorized: true,
104104
},
105+
idleTimeoutMs: 30000,
105106
};
106107
});
107108

@@ -121,6 +122,9 @@ describe('DbConnectionOptions', () => {
121122
migrations: postgresMigrations,
122123
connectTimeoutMS: 20000,
123124
ssl: false,
125+
extra: {
126+
idleTimeoutMillis: 30000,
127+
},
124128
});
125129
});
126130

packages/cli/src/databases/__tests__/db-connection.test.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { DatabaseConfig } from '@n8n/config';
12
import type { Migration } from '@n8n/db';
23
import * as migrationHelper from '@n8n/db';
34
import { DataSource, type DataSourceOptions } from '@n8n/typeorm';
@@ -17,6 +18,7 @@ describe('DbConnection', () => {
1718
let dbConnection: DbConnection;
1819
const migrations = [{ name: 'TestMigration1' }, { name: 'TestMigration2' }] as Migration[];
1920
const errorReporter = mock<ErrorReporter>();
21+
const databaseConfig = mock<DatabaseConfig>();
2022
const dataSource = mockDeep<DataSource>({ options: { migrations } });
2123
const connectionOptions = mockDeep<DbConnectionOptions>();
2224
const postgresOptions: DataSourceOptions = {
@@ -35,7 +37,7 @@ describe('DbConnection', () => {
3537
connectionOptions.getOptions.mockReturnValue(postgresOptions);
3638
(DataSource as jest.Mock) = jest.fn().mockImplementation(() => dataSource);
3739

38-
dbConnection = new DbConnection(errorReporter, connectionOptions);
40+
dbConnection = new DbConnection(errorReporter, connectionOptions, databaseConfig);
3941
});
4042

4143
describe('init', () => {
@@ -174,5 +176,29 @@ describe('DbConnection', () => {
174176

175177
expect(dataSource.query).not.toHaveBeenCalled();
176178
});
179+
180+
it('should execute ping on schedule', async () => {
181+
jest.useFakeTimers();
182+
try {
183+
// ARRANGE
184+
dbConnection = new DbConnection(
185+
errorReporter,
186+
connectionOptions,
187+
mock<DatabaseConfig>({
188+
pingIntervalSeconds: 1,
189+
}),
190+
);
191+
192+
const pingSpy = jest.spyOn(dbConnection as any, 'ping');
193+
194+
// @ts-expect-error private property
195+
dbConnection.scheduleNextPing();
196+
jest.advanceTimersByTime(1000);
197+
198+
expect(pingSpy).toHaveBeenCalled();
199+
} finally {
200+
jest.useRealTimers();
201+
}
202+
});
177203
});
178204
});

packages/cli/src/databases/db-connection-options.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,9 @@ export class DbConnectionOptions {
131131
migrations: postgresMigrations,
132132
connectTimeoutMS: postgresConfig.connectionTimeoutMs,
133133
ssl,
134+
extra: {
135+
idleTimeoutMillis: postgresConfig.idleTimeoutMs,
136+
},
134137
};
135138
}
136139

packages/cli/src/databases/db-connection.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { inTest } from '@n8n/backend-common';
2+
import { DatabaseConfig } from '@n8n/config';
23
import type { Migration } from '@n8n/db';
34
import { wrapMigration } from '@n8n/db';
45
import { Memoized } from '@n8n/decorators';
@@ -7,6 +8,8 @@ import { DataSource } from '@n8n/typeorm';
78
import { ErrorReporter } from 'n8n-core';
89
import { DbConnectionTimeoutError, ensureError } from 'n8n-workflow';
910

11+
import { Time } from '@/constants';
12+
1013
import { DbConnectionOptions } from './db-connection-options';
1114

1215
type ConnectionState = {
@@ -28,6 +31,7 @@ export class DbConnection {
2831
constructor(
2932
private readonly errorReporter: ErrorReporter,
3033
private readonly connectionOptions: DbConnectionOptions,
34+
private readonly databaseConfig: DatabaseConfig,
3135
) {
3236
this.dataSource = new DataSource(this.options);
3337
Container.set(DataSource, this.dataSource);
@@ -80,9 +84,12 @@ export class DbConnection {
8084
}
8185
}
8286

83-
/** Ping DB connection every 2 seconds */
87+
/** Ping DB connection every `pingIntervalSeconds` seconds to check if it is still alive. */
8488
private scheduleNextPing() {
85-
this.pingTimer = setTimeout(async () => await this.ping(), 2000);
89+
this.pingTimer = setTimeout(
90+
async () => await this.ping(),
91+
this.databaseConfig.pingIntervalSeconds * Time.seconds.toMilliseconds,
92+
);
8693
}
8794

8895
private async ping() {

0 commit comments

Comments
 (0)