From 99c30edf1da2e3886fd2e2ca60139907e2e4438c Mon Sep 17 00:00:00 2001 From: James Gee <1285296+geemanjs@users.noreply.github.com> Date: Mon, 8 Dec 2025 12:00:14 +0100 Subject: [PATCH] fix(core): Make workers memory constraints more container aware (#22698) --- packages/@n8n/api-types/src/scaling.ts | 17 ++++ .../src/scaling/worker-status.service.ee.ts | 23 +++++- .../frontend/@n8n/i18n/src/locales/en.json | 1 + .../components/WorkerCard.vue | 12 ++- .../components/WorkerChartsAccordion.vue | 36 ++++++--- .../WorkerMemoryMonitorAccordion.vue | 81 +++++++++++++++++++ .../orchestration.ee/orchestration.utils.ts | 8 +- 7 files changed, 160 insertions(+), 18 deletions(-) create mode 100644 packages/frontend/editor-ui/src/features/settings/orchestration.ee/components/WorkerMemoryMonitorAccordion.vue diff --git a/packages/@n8n/api-types/src/scaling.ts b/packages/@n8n/api-types/src/scaling.ts index 30db754a112..f2dcbb52b95 100644 --- a/packages/@n8n/api-types/src/scaling.ts +++ b/packages/@n8n/api-types/src/scaling.ts @@ -13,6 +13,23 @@ export type RunningJobSummary = { export type WorkerStatus = { senderId: string; runningJobsSummary: RunningJobSummary[]; + isInContainer: boolean; + process: { + memory: { + available: number; + constraint: number; + rss: number; + heapTotal: number; + heapUsed: number; + }; + uptime: number; + }; + host: { + memory: { + total: number; + free: number; + }; + }; freeMem: number; totalMem: number; uptime: number; diff --git a/packages/cli/src/scaling/worker-status.service.ee.ts b/packages/cli/src/scaling/worker-status.service.ee.ts index b5668138222..5f86bb570ed 100644 --- a/packages/cli/src/scaling/worker-status.service.ee.ts +++ b/packages/cli/src/scaling/worker-status.service.ee.ts @@ -3,6 +3,7 @@ import { OnPubSubEvent } from '@n8n/decorators'; import { Service } from '@n8n/di'; import { InstanceSettings } from 'n8n-core'; import os from 'node:os'; +import process from 'node:process'; import { N8N_VERSION } from '@/constants'; import { Push } from '@/push'; @@ -46,9 +47,29 @@ export class WorkerStatusService { } private generateStatus(): WorkerStatus { + const constrainedMemory = process.constrainedMemory(); + + // See https://github.com/nodejs/node/issues/59227 for information about why we cap at MAX_SAFE_INTEGER + // The number 18446744073709552000 does come back when running in a container with no constraints + const isInContainer = constrainedMemory > 0 && constrainedMemory < Number.MAX_SAFE_INTEGER; return { senderId: this.instanceSettings.hostId, runningJobsSummary: this.jobProcessor.getRunningJobsSummary(), + isInContainer, + process: { + memory: { + available: process.availableMemory(), + constraint: process.constrainedMemory(), + ...process.memoryUsage(), + }, + uptime: process.uptime(), + }, + host: { + memory: { + total: os.totalmem(), + free: os.freemem(), + }, + }, freeMem: os.freemem(), totalMem: os.totalmem(), uptime: process.uptime(), @@ -73,6 +94,6 @@ export class WorkerStatusService { if (cpus.length === 0) return 'no CPU info'; - return `${cpus.length}x ${cpus[0].model} - speed: ${cpus[0].speed}`; + return `${cpus.length}x ${cpus[0].model}`; } } diff --git a/packages/frontend/@n8n/i18n/src/locales/en.json b/packages/frontend/@n8n/i18n/src/locales/en.json index 07e2932c510..7d67132a5c9 100644 --- a/packages/frontend/@n8n/i18n/src/locales/en.json +++ b/packages/frontend/@n8n/i18n/src/locales/en.json @@ -1081,6 +1081,7 @@ "workerList.item.jobList.empty": "No current jobs", "workerList.item.jobListTitle": "Current Jobs", "workerList.item.netListTitle": "Network Interfaces", + "workerList.item.memoryMonitorTitle": "Memory Monitoring", "workerList.item.chartsTitle": "Performance Monitoring", "workerList.item.copyAddressToClipboard": "Address copied to clipboard", "workerList.actionBox.title": "Available on the Enterprise plan", diff --git a/packages/frontend/editor-ui/src/features/settings/orchestration.ee/components/WorkerCard.vue b/packages/frontend/editor-ui/src/features/settings/orchestration.ee/components/WorkerCard.vue index 240cb670882..e465e8fe0ed 100644 --- a/packages/frontend/editor-ui/src/features/settings/orchestration.ee/components/WorkerCard.vue +++ b/packages/frontend/editor-ui/src/features/settings/orchestration.ee/components/WorkerCard.vue @@ -7,6 +7,7 @@ import { averageWorkerLoadFromLoadsAsString, memAsGb } from '../orchestration.ut import WorkerJobAccordion from './WorkerJobAccordion.vue'; import WorkerNetAccordion from './WorkerNetAccordion.vue'; import WorkerChartsAccordion from './WorkerChartsAccordion.vue'; +import WorkerMemoryMonitorAccordion from './WorkerMemoryMonitorAccordion.vue'; import { sortByProperty } from '@n8n/utils/sort/sortByProperty'; import { useI18n } from '@n8n/i18n'; @@ -69,8 +70,13 @@ onBeforeUnmount(() => { data-test-id="worker-card-name" > Name: {{ worker.senderId }} ({{ worker.hostname }})
- Average Load: {{ averageWorkerLoadFromLoadsAsString(worker.loadAvg ?? [0]) }} | Free Memory: - {{ memAsGb(worker.freeMem).toFixed(2) }}GB / {{ memAsGb(worker.totalMem).toFixed(2) }}GB + Average Load: {{ averageWorkerLoadFromLoadsAsString(worker.loadAvg ?? [0]) }} | Free memory: + {{ memAsGb(worker.process.memory.available) }}GB / + {{ + memAsGb( + worker.isInContainer ? worker.process.memory.constraint : worker.host.memory.total, + ) + }}GB {{ stale ? ' (stale)' : '' }} @@ -81,9 +87,11 @@ onBeforeUnmount(() => { ago | n8n-Version: {{ worker.version }} | Architecture: {{ worker.arch }} ( {{ worker.platform }}) | Uptime: {{ upTime(worker.uptime) }} +
+