I have a Node.js web server built with Express.js, and I'm trying to optimize memory usage and the V8 garbage collector. My understanding is that increasing the heap size allows more memory to be allocated before triggering garbage collection (GC), which can improve performance in some cases.
To achieve this, I wrote a Bash script to dynamically configure --max-heap-size
and --max-old-space-size
based on the available memory in the Docker container. Here is the script:
/start-node.sh
#!/bin/sh
# memory limint on Docker container
MEMORY_LIMIT=$(cat /sys/fs/cgroup/memory/memory.limit_in_bytes)
# memory limint in MB
MEMORY_LIMIT_MB=$((MEMORY_LIMIT / 1024 / 1024))
# if memory limint is too bigger (9223372036854771712 is no limit), set to 2 GB
if [ "$MEMORY_LIMIT" -gt 9223372036854771712 ]; then
MEMORY_LIMIT_MB=2048
fi
# heap is 75% of total
HEAP_SIZE_MB=$((MEMORY_LIMIT_MB * 75 / 100))
# Old Generation is 60-80% of heap
OLD_SPACE_SIZE_MB=$((HEAP_SIZE_MB * 70 / 100))
echo "Memory limit: ${MEMORY_LIMIT_MB} MB"
echo "Setting max heap size to: ${HEAP_SIZE_MB} MB"
echo "Setting max old space size to: ${OLD_SPACE_SIZE_MB} MB"
node --max-heap-size=$((HEAP_SIZE_MB * 1024)) --max-old-space-size=${OLD_SPACE_SIZE_MB} ./server.js
My Questions:
--max-heap-size
and --max-old-space-size
?Is this approach correct
The definition of "correct" depends on what you want. As a general idea, sure, something like this can work.
"I'm trying to optimize memory usage" usually means you want to reduce memory usage. Cranking up the heap size to "improve performance in some cases" (at the cost of worse memory consumption) is the opposite. This example illustrates that there isn't a single "correct" approach for everyone.
Are there any improvements or best practices I should consider
Some of the details of your script are a bit questionable.
(1) You say "9223372036854771712 is no limit", which presumably means that's the value you get when no limit is configured. In that case, the -gt
condition on the next line will never be true, because no reported value will ever be greater than that threshold.
(2) Aside from (1), the logic you've implemented is "if the limit is 8589934592 GB or more, set it to 2 GB instead". But a limit of 8589934591 GB is okay? That's a bit surprising. Also, why 2 GB in particular? Why not 1, or 4, or 57?
(3) --max-heap-size
takes its value in MB, just like --max-old-space-size
. So that multiplication with 1024 is unlikely to be what you want.
(4) Why force any particular distribution of total heap size among spaces based on guesswork, when you can just let V8 decide?
Is it safe to assume that allocating 75% of the total memory to the heap and 70% of the heap to the old generation is a good rule of thumb?
I'm not sure there's any generally applicable rule of thumb there. For some apps in some container configurations those values might work okay.
But it certainly depends on the container size: for very small containers, leaving only 25% of their memory for all purposes other than the managed heap might be way too little; for very large containers it might be a waste. And even if it's a good match for the container size, it might be just right for one application, and way too little for another application (that just so happens to use more off-heap memory and less on-heap memory).
To determine reasonable values for your app in your container, you'll have to inspect its requirements.
When in doubt, I'd recommend to trust the built-in defaults (unless you have concrete reason to believe that they're not a good match for you), rather than making your own guesses and hardcoding them as a rule of thumb. Or, you know, configure whatever limits you want; if they end up being a problem in practice, you'll probably notice, and otherwise it's not a problem anyway.