From 8b4acb683183f945e785677b406460847bbd46be Mon Sep 17 00:00:00 2001 From: Lorenzo Gabriele Date: Mon, 28 Sep 2026 13:29:10 +0200 Subject: [PATCH 1/3] fix: derive node heap limit from cgroup memory at startup Hardcoded --max-old-space-size=4608 assumed a fixed container memory limit, causing heavy GC thrash (minutes of scavenge/mark-sweep) before the process finally OOM-crashes when the actual k8s limit differs. Reading process.constrainedMemory() at entrypoint time sets the heap ceiling to 75% of the real cgroup limit, so V8 hits the fatal threshold sooner and the pod restarts faster. --- Dockerfile | 1 + compute-heap.mjs | 11 +++++++++++ entrypoint.sh | 4 +++- 3 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 compute-heap.mjs diff --git a/Dockerfile b/Dockerfile index f4720dec2..1f0974cad 100644 --- a/Dockerfile +++ b/Dockerfile @@ -19,6 +19,7 @@ COPY --from=builder --chown=docker:docker docs docs COPY --from=builder node_modules node_modules COPY --chown=docker:docker tsconfig.src.json tsconfig.json COPY entrypoint.sh entrypoint.sh +COPY compute-heap.mjs compute-heap.mjs RUN adduser -u 2004 -D docker &&\ chmod +x entrypoint.sh diff --git a/compute-heap.mjs b/compute-heap.mjs new file mode 100644 index 000000000..2dbb8de18 --- /dev/null +++ b/compute-heap.mjs @@ -0,0 +1,11 @@ +import os from "node:os"; + +const constrained = + typeof process.constrainedMemory === "function" + ? process.constrainedMemory() + : 0; +const limitBytes = constrained || os.totalmem(); +const limitMB = Math.floor(limitBytes / 1024 / 1024); +const heapMB = Math.floor(limitMB * 0.75); + +process.stdout.write(String(heapMB)); diff --git a/entrypoint.sh b/entrypoint.sh index b269ddafd..d3f5c6f1f 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -1,6 +1,8 @@ #!/bin/sh +HEAP_MB=$(node /compute-heap.mjs) + exec node \ - --max-old-space-size=4608 \ + --max-old-space-size="${HEAP_MB}" \ --max-semi-space-size=128 \ /dist/src/index.js From f6f149eb54b0102e849f0ae13224286637560353 Mon Sep 17 00:00:00 2001 From: Lorenzo Gabriele Date: Mon, 28 Sep 2026 13:50:20 +0200 Subject: [PATCH 2/3] fix: Add compute-heap.mjs to .dockerignore --- .dockerignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.dockerignore b/.dockerignore index 97285a9a1..db7aef61f 100644 --- a/.dockerignore +++ b/.dockerignore @@ -9,6 +9,7 @@ !/tsconfig.json !/tsconfig.src.json !/entrypoint.sh +!/compute-heap.mjs # Ignore unnecessary files inside allowed directories # This should go after the allowed directories From 04c30377d3b285f32347900a5282499e5eae3974 Mon Sep 17 00:00:00 2001 From: Lorenzo Gabriele Date: Mon, 28 Sep 2026 14:38:26 +0200 Subject: [PATCH 3/3] fix: derive semi-space size from cgroup budget too max-semi-space-size was still fixed at 128MB, additive on top of the derived old-space size. Since new-space is two semi-spaces, its worst-case footprint (2x the flag value) could push total V8 heap past the cgroup limit. Now both old-space and semi-space are sized from the same memory budget, with new-space reserved first. --- compute-heap.mjs | 14 ++++++++++++-- entrypoint.sh | 6 ++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/compute-heap.mjs b/compute-heap.mjs index 2dbb8de18..45473cde2 100644 --- a/compute-heap.mjs +++ b/compute-heap.mjs @@ -6,6 +6,16 @@ const constrained = : 0; const limitBytes = constrained || os.totalmem(); const limitMB = Math.floor(limitBytes / 1024 / 1024); -const heapMB = Math.floor(limitMB * 0.75); -process.stdout.write(String(heapMB)); +// Reserve 25% headroom for non-V8-heap usage (native buffers, stack, code space, RSS overhead). +const budgetMB = Math.floor(limitMB * 0.75); + +// V8's new-space is TWO semi-spaces (from-space + to-space), so its worst-case +// footprint is 2x --max-semi-space-size. Size it as a fraction of the budget, +// clamped to V8's own sane range, then subtract it before sizing old-space so +// old-space + new-space never exceeds the budget. +const semiSpaceMB = Math.min(128, Math.max(16, Math.floor(budgetMB * 0.05))); +const newSpaceMB = semiSpaceMB * 2; +const oldSpaceMB = Math.max(1, budgetMB - newSpaceMB); + +process.stdout.write(`${oldSpaceMB} ${semiSpaceMB}`); diff --git a/entrypoint.sh b/entrypoint.sh index d3f5c6f1f..cf54c93fd 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -1,8 +1,10 @@ #!/bin/sh -HEAP_MB=$(node /compute-heap.mjs) +read -r HEAP_MB SEMI_MB <