Skip to content

Commit ab674cd

Browse files
Cap web/backend Node heaps from the container memory limit
The web and backend Node processes share the container's memory limit with zoekt, but V8's default old-space heap doesn't track the cgroup limit — so `web` caps near ~4GB regardless of a larger container and OOMs there once its working set grows (supervisord respawns it, but the liveness probe can escalate the gap into a full pod restart). entrypoint.sh now derives a per-process --max-old-space-size from the container's memory limit (cgroup v2 then v1): web 55%, backend 20% by default. The values are wired into the web/backend commands in supervisord.conf (per-process, so the two heaps don't over-commit the shared cgroup). Falls back to V8's default (0) when no limit is detected. Overridable via WEB_HEAP_PERCENT / BACKEND_HEAP_PERCENT or absolute WEB_MAX_OLD_SPACE_SIZE / BACKEND_MAX_OLD_SPACE_SIZE; the Dockerfile sets 0 defaults as a backstop for supervisord interpolation. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent d02f61c commit ab674cd

3 files changed

Lines changed: 57 additions & 2 deletions

File tree

Dockerfile

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,12 @@ ENV NEXT_TELEMETRY_DISABLED=1
164164
ENV DATA_DIR=/data
165165
ENV DATA_CACHE_DIR=$DATA_DIR/.sourcebot
166166
ENV SOURCEBOT_PUBLIC_KEY_PATH=/app/public.pem
167+
# Per-process Node heap caps (MB) for the web/backend processes, consumed by
168+
# supervisord.conf. entrypoint.sh overrides these at runtime, computing them from
169+
# the container's memory limit; these defaults (0 = V8 default) are a backstop so
170+
# supervisord's %(ENV_...)s interpolation resolves even if entrypoint is bypassed.
171+
ENV WEB_MAX_OLD_SPACE_SIZE=0
172+
ENV BACKEND_MAX_OLD_SPACE_SIZE=0
167173
# PAPIK = Project API Key
168174
# Note that this key does not need to be kept secret, so it's not
169175
# necessary to use Docker build secrets here.

entrypoint.sh

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,5 +196,51 @@ DATABASE_URL="$DATABASE_URL" yarn workspace @sourcebot/db prisma:migrate:prod
196196
# Create the log directory if it doesn't exist
197197
mkdir -p /var/log/sourcebot
198198

199+
# --- Node.js heap sizing ---------------------------------------------------
200+
# `web` and `backend` are Node processes that share this container's memory
201+
# limit with `zoekt`. V8's default old-space heap does NOT track the cgroup
202+
# limit, so by default `web` caps near ~4GB regardless of a larger container
203+
# (and OOMs there once its working set grows). Derive a per-process
204+
# --max-old-space-size from the container's memory limit so each Node process
205+
# gets an appropriate slice without the two heaps over-committing the cgroup.
206+
# These values are passed to the `web`/`backend` commands in supervisord.conf.
207+
# A value of 0 means "let V8 pick its own default" (used when no limit is set).
208+
#
209+
# Optional overrides (env):
210+
# WEB_HEAP_PERCENT / BACKEND_HEAP_PERCENT - % of the container limit
211+
# (defaults: 55 / 20)
212+
# WEB_MAX_OLD_SPACE_SIZE / BACKEND_MAX_OLD_SPACE_SIZE - absolute MB; when set,
213+
# used as-is (skips the %).
214+
container_mem_limit_mb() {
215+
_limit=""
216+
if [ -r /sys/fs/cgroup/memory.max ]; then # cgroup v2
217+
_limit=$(cat /sys/fs/cgroup/memory.max 2>/dev/null)
218+
elif [ -r /sys/fs/cgroup/memory/memory.limit_in_bytes ]; then # cgroup v1
219+
_limit=$(cat /sys/fs/cgroup/memory/memory.limit_in_bytes 2>/dev/null)
220+
fi
221+
# "max" (v2) or the v1 unlimited sentinel (~2^63) both mean "no limit".
222+
case "$_limit" in
223+
''|max) echo 0; return;;
224+
esac
225+
if [ "$_limit" -gt 9223372036854000000 ] 2>/dev/null; then echo 0; return; fi
226+
echo $(( _limit / 1024 / 1024 ))
227+
}
228+
229+
resolve_heap_mb() { # $1=explicit override $2=percent $3=default-percent
230+
if [ -n "$1" ]; then echo "$1"; return; fi
231+
_pct="${2:-$3}"
232+
if [ "$MEM_LIMIT_MB" -gt 0 ]; then echo $(( MEM_LIMIT_MB * _pct / 100 )); else echo 0; fi
233+
}
234+
235+
MEM_LIMIT_MB=$(container_mem_limit_mb)
236+
export WEB_MAX_OLD_SPACE_SIZE=$(resolve_heap_mb "$WEB_MAX_OLD_SPACE_SIZE" "$WEB_HEAP_PERCENT" 55)
237+
export BACKEND_MAX_OLD_SPACE_SIZE=$(resolve_heap_mb "$BACKEND_MAX_OLD_SPACE_SIZE" "$BACKEND_HEAP_PERCENT" 20)
238+
239+
if [ "$MEM_LIMIT_MB" -gt 0 ]; then
240+
echo -e "\e[34m[Info] Container memory limit: ${MEM_LIMIT_MB}MB. Node heap caps (--max-old-space-size) — web: ${WEB_MAX_OLD_SPACE_SIZE}MB, backend: ${BACKEND_MAX_OLD_SPACE_SIZE}MB.\e[0m"
241+
else
242+
echo -e "\e[34m[Info] No container memory limit detected; using V8 default heap sizing for web/backend.\e[0m"
243+
fi
244+
199245
# Run supervisord
200246
exec supervisord -c /etc/supervisor/conf.d/supervisord.conf

supervisord.conf

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ stdout_logfile_maxbytes=0
1414
redirect_stderr=true
1515

1616
[program:web]
17-
command=./prefix-output.sh node packages/web/server.js
17+
# --max-old-space-size is computed from the container memory limit in entrypoint.sh
18+
# (0 = V8 default). Per-process so web/backend don't over-commit the shared cgroup.
19+
command=./prefix-output.sh node --max-old-space-size=%(ENV_WEB_MAX_OLD_SPACE_SIZE)s packages/web/server.js
1820
priority=20
1921
autostart=true
2022
autorestart=true
@@ -24,7 +26,8 @@ stdout_logfile_maxbytes=0
2426
redirect_stderr=true
2527

2628
[program:backend]
27-
command=./prefix-output.sh node packages/backend/dist/index.js
29+
# See [program:web]; cap computed from the container memory limit in entrypoint.sh.
30+
command=./prefix-output.sh node --max-old-space-size=%(ENV_BACKEND_MAX_OLD_SPACE_SIZE)s packages/backend/dist/index.js
2831
priority=20
2932
autostart=true
3033
autorestart=true

0 commit comments

Comments
 (0)