From 33eb2e5963b7e0edaa7fae1fde412222d6e5e364 Mon Sep 17 00:00:00 2001 From: Arda TANRIKULU Date: Fri, 1 Nov 2024 14:40:22 +0300 Subject: [PATCH] enhance(gateway-cli): respect available memory while forking processes --- .changeset/serious-buses-learn.md | 5 +++++ packages/gateway/src/cli.ts | 11 ++++++----- packages/gateway/src/getMaxConcurrency.ts | 21 +++++++++++++++++++++ 3 files changed, 32 insertions(+), 5 deletions(-) create mode 100644 .changeset/serious-buses-learn.md create mode 100644 packages/gateway/src/getMaxConcurrency.ts diff --git a/.changeset/serious-buses-learn.md b/.changeset/serious-buses-learn.md new file mode 100644 index 000000000..d46ecd67b --- /dev/null +++ b/.changeset/serious-buses-learn.md @@ -0,0 +1,5 @@ +--- +'@graphql-hive/gateway': patch +--- + +Respect available memory on forking the processes not just CPU diff --git a/packages/gateway/src/cli.ts b/packages/gateway/src/cli.ts index e1f8496db..c426297cc 100644 --- a/packages/gateway/src/cli.ts +++ b/packages/gateway/src/cli.ts @@ -25,6 +25,7 @@ import { DefaultLogger } from '@graphql-mesh/utils'; import parseDuration from 'parse-duration'; import { addCommands } from './commands/index'; import { createDefaultConfigPaths } from './config'; +import { getMaxConcurrency } from './getMaxConcurrency'; import type { ServerConfig } from './server'; export type GatewayCLIConfig = ( @@ -178,9 +179,9 @@ export type AddCommand = (ctx: CLIContext, cli: CLI) => void; // we dont use `Option.default()` in the command definitions because we want the CLI options to // override the config file (with option defaults, config file will always be overwritten) -const maxAvailableFork = Math.max(availableParallelism() - 1, 1); +const maxFork = getMaxConcurrency(); export const defaultOptions = { - fork: process.env['NODE_ENV'] === 'production' ? maxAvailableFork : 1, + fork: process.env['NODE_ENV'] === 'production' ? maxFork : 1, host: platform().toLowerCase() === 'win32' || // is WSL? @@ -200,7 +201,7 @@ let cli = new Command() .addOption( new Option( '--fork ', - `count of workers to spawn. uses "${maxAvailableFork}" (available parallelism) workers when NODE_ENV is "production", otherwise "1" (the main) worker (default: ${JSON.stringify(defaultOptions.fork)}`, + `count of workers to spawn. uses "${maxFork}" (available parallelism) workers when NODE_ENV is "production", otherwise "1" (the main) worker (default: ${JSON.stringify(defaultOptions.fork)}`, ) .env('FORK') .argParser((v) => { @@ -208,9 +209,9 @@ let cli = new Command() if (isNaN(count)) { throw new InvalidArgumentError('not a number.'); } - if (count > maxAvailableFork) { + if (count > maxFork) { throw new InvalidArgumentError( - `exceedes number of available parallelism "${maxAvailableFork}".`, + `exceedes number of available parallelism "${maxFork}".`, ); } return count; diff --git a/packages/gateway/src/getMaxConcurrency.ts b/packages/gateway/src/getMaxConcurrency.ts new file mode 100644 index 000000000..a5fb0f8dd --- /dev/null +++ b/packages/gateway/src/getMaxConcurrency.ts @@ -0,0 +1,21 @@ +import { availableParallelism, freemem } from 'node:os'; + +function getFreeMemInGb() { + return freemem() / 1024 ** 3; +} + +function getMaxConcurrencyPerMem() { + return parseInt(String(getFreeMemInGb())); +} + +function getMaxConcurrencyPerCpu() { + return availableParallelism(); +} + +export function getMaxConcurrency() { + const result = Math.min(getMaxConcurrencyPerMem(), getMaxConcurrencyPerCpu()); + if (result < 1) { + return 1; + } + return result; +}