Skip to content

Instantly share code, notes, and snippets.

@wkentdag
Created January 18, 2020 00:15
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save wkentdag/53e251ded106a28d12457f3294334dd3 to your computer and use it in GitHub Desktop.
Save wkentdag/53e251ded106a28d12457f3294334dd3 to your computer and use it in GitHub Desktop.

Debugging Memory Issues in Node

This guide summarizes some helpful strategies and resources encountered while debugging memory usage in the hashicorp/learn repository.

Diagnosing memory issues

In JS, memory issues occur when old objects aren't garbage collected, and pile up on the heap. We can observe the issue by noting performance degradation over time, or by maxing out memory within a single program.

Typically memory issues are discovered because performance slows down. You might also encounter this fun error if your program runs out of memory:

FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory

Terminology

A leak can be observed over time - for example, a server whose response time and memory usage always increases.

A bloat simply means that a given program is using an excessive amount of memory, but it doesn't get worse with time - for example, a build command that causes the heap to overflow.

Quick fix

Your first step towards alleviating the immediate issue is to increase Node's default memory limit, which is set quite low.

You can pass a runtime flag:

node --max-old-space-size=4096 foo.js

or export an environment variable:

export NODE_OPTIONS='--max-old-space-size=4096'
node foo.js

The size is defined in megabytes and can theoretically be any value up to your CPU's memory limit.

This obviously won't fix your underlying problem but hopefully it will temporarily alleviate the issue, buying you some time to debug it.

Debugging

Now the fun part.

Flamegraphs

I found it useful to start with a flamegraph to get a sense of what the program was spending its time doing under the hood. flamebearer makes it trivial to generate these graphs:

npm i -g flamebearer

# profile our program
node --prof foo.js

# generate flamegraph
node --prof-process --preprocess -j isolate*.log | flamebearer

This may uncover some interesting results. In the case of learn, it confirmed our hunch that the build step was spending a lot of time sorting out the track data that tie our pages together.

Heap Profiling

The surest way to get to the source of the issue is to take some snapshots of the heap using V8's built-in tools. Start by passing the inspect flag:

node --inspect foo.js

From there, navigate to chrome://inspect, and select your node process, opening up Chrome DevTools.

You can take individual snapshots of the heap to observe its contents at a given moment in time. You can also take an allocation profile, which records along a timeline and displays a bar chart of memory allocation, allowing you to drill into specific parts of the timeline.

Most of the code you encounter in these profiles will probably be beneath the level of your own code - so when you see function names that you recognize, take note.

Resources

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment