diff --git a/packages/query/CHANGELOG.md b/packages/query/CHANGELOG.md index 244af18a0e..22916917be 100644 --- a/packages/query/CHANGELOG.md +++ b/packages/query/CHANGELOG.md @@ -8,6 +8,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Revert yargs version +### Changed +- Update how idle psql pool connections are handled (#2520) + ## [2.14.0] - 2024-08-05 ### Changed - Update dependencies (#2518) diff --git a/packages/query/src/configure/configure.module.ts b/packages/query/src/configure/configure.module.ts index 40303ca559..930bf5f509 100644 --- a/packages/query/src/configure/configure.module.ts +++ b/packages/query/src/configure/configure.module.ts @@ -13,10 +13,14 @@ import {debugPgClient} from './x-postgraphile/debugClient'; async function ensurePool(poolConfig: PoolConfig): Promise { const pgPool = new Pool(poolConfig); try { - await pgPool.connect(); + const pgClient = await pgPool.connect(); + // Release the client back to the pool as it's no longer needed + pgClient.release(); } catch (e: any) { if (JSON.stringify(e.message).includes(CONNECTION_SSL_ERROR_REGEX)) { poolConfig.ssl = undefined; + // end the pool and recreate it without ssl + await pgPool.end(); return ensurePool(poolConfig); } } diff --git a/packages/query/src/graphql/graphql.module.ts b/packages/query/src/graphql/graphql.module.ts index 7dc33b6132..39f5bb4ee9 100644 --- a/packages/query/src/graphql/graphql.module.ts +++ b/packages/query/src/graphql/graphql.module.ts @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-3.0 import assert from 'assert'; +import {setInterval} from 'timers'; import PgPubSub from '@graphile/pg-pubsub'; import {Module, OnModuleDestroy, OnModuleInit} from '@nestjs/common'; import {HttpAdapterHost} from '@nestjs/core'; @@ -19,7 +20,7 @@ import {NextFunction, Request, Response} from 'express'; import PinoLogger from 'express-pino-logger'; import {execute, GraphQLSchema, subscribe} from 'graphql'; import {set} from 'lodash'; -import {Pool} from 'pg'; +import {Pool, PoolClient} from 'pg'; import {makePluginHook} from 'postgraphile'; import {SubscriptionServer} from 'subscriptions-transport-ws'; import {Config} from '../configure'; @@ -127,6 +128,18 @@ export class GraphqlModule implements OnModuleInit, OnModuleDestroy { } } + private setupKeepAlive(pgClient: PoolClient) { + setInterval(() => { + void (async () => { + try { + await pgClient.query('SELECT 1'); + } catch (err) { + getLogger('db').error('Schema listener client keep-alive query failed: ', err); + } + })(); + }, this.config.get('sl-keep-alive-interval')); + } + private async createServer() { const app = this.httpAdapterHost.httpAdapter.getInstance(); const httpServer = this.httpAdapterHost.httpAdapter.getHttpServer(); @@ -165,6 +178,14 @@ export class GraphqlModule implements OnModuleInit, OnModuleDestroy { const pgClient = await this.pgPool.connect(); await pgClient.query(`LISTEN "${hashName(dbSchema, 'schema_channel', '_metadata')}"`); + // Set up a keep-alive interval to prevent the connection from being killed + this.setupKeepAlive(pgClient); + + pgClient.on('error', (err: Error) => { + getLogger('db').error('PostgreSQL schema listener client error: ', err); + process.exit(1); + }); + pgClient.on('notification', (msg) => { if (msg.payload === 'schema_updated') { void this.schemaListener(dbSchema, options); diff --git a/packages/query/src/yargs.ts b/packages/query/src/yargs.ts index 7b7f00ae34..e82ab13a20 100644 --- a/packages/query/src/yargs.ts +++ b/packages/query/src/yargs.ts @@ -151,6 +151,12 @@ export function getYargsOption() { type: 'boolean', default: false, }, + 'sl-keep-alive-interval': { + demandOption: false, + describe: 'Schema listener keep-alive interval in milliseconds', + type: 'number', + default: 180000, + }, }); }