Reduce memory usage of NodeJS apps inside Docker

fbjorn

Denis

Posted on July 27, 2023

Reduce memory usage of NodeJS apps inside Docker

Before diving in, I must admit that the title is a bit vague. Let me describe the issue first, cause the solution below is not a silver bullet and it's suitable for specific runtime environments.

In our company we use Google Cloud Run to deploy web applications, and every app is built into a docker image. For now we use the default memory limit by Cloud Run which is 256 MB per container. Recently we started to notice that the part of applications go beyond this limit, causing a container to restart and in some cases even resulting to downtime of a service.

These applications run as Node processes (NextJS server and SvelteKit server), and docker entrypoint is just a pnpm run start, that executes the corresponding command under the hood.

Troubleshooting

Unfortunately, it's impossible to get inside a running instance in Cloud Run, so the only way to debug a problem is via logs. Web interface doesn't provide you any information about running processes, just a chart with memory utilization from your containers:

Image description

On this chart, we can see that sometimes it's just not enough memory for a service.

My initial idea was to periodically run top inside a container and log the output. Then try to see how different conditions like huge traffic affect the memory. There's a tool that helps you run multiple processes inside a docker container called multi-start. I slightly modified its code to execute top -bcn1 -o %MEM -w256 by some interval and log the result to stdout. But just after installing it to the image and deploying it to Google Cloud I noticed something strange:

Image description

1) Total memory consumption according to top is 160 MB. But the chart above says it takes up to 90% of memory. Well, memory calculation can be interesting sometimes
2) For some reason, the container thinks it has 1 Gb RAM allocated, yet the limit in Cloud Run is set to 256 MB
3) pnpm run start consumes almost the same amount of memory as NextJS server! Yet it's just a runner for an actual command

Solution

In fact, the only thing I had to do is to replace pnpm run start with a corresponding script from package.json, so in case of NextJS apps it was node ./node_modules/next/dist/bin/next start and node ./build for SvelteKit (you might have a different setup).

The results are prominent:

Image description

It immediately saved almost 60 MB or memory, which is a lot in such circumstances. See how the changes are reflected in the dashboard:

Image description

Conclusion

This indeed may sound like an edge case, but if you run your node application in an environment with such a limited resources, just try to invoke the scripts directly, without wrapping them into pnpm command.

It also sounds very strange to me why pnpm is this eager. If anyone has some ideas, please share them.

Hope it helps 👋

💖 💪 🙅 🚩
fbjorn
Denis

Posted on July 27, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related