Profiling Node
Recently I ran into a memory leak while running a node script to migrate ~300MM rows from Postgres to DynamoDB.
The first step was to add node-memwatch:
const memwatch = require('memwatch');
memwatch.on('leak', (info) => console.log("Possible leak: ", info));
This outputs an info object similar to:
{ start: Fri, 29 Jun 2012 14:12:13 GMT,
end: Fri, 29 Jun 2012 14:12:33 GMT,
growth: 67984,
reason: 'heap growth over 5 consecutive GCs (20s) - 11.67 mb/hr' }
Unfortantely, this identifies issues that are more a symptom or warning than a cause.
Node Inspector
Thankfully, Node has a built-in inspect argument:
package.json
"scripts": {
"run": "node --inspect index.js"
}
Adding --inspect
attaches the node debugger, which you can read more on here. I found the easy way to profile a Node app is to use the NiM Chrome Extension, which produces output similar to this:
If necessary, you can also bump up memory as well, but I would only do this if necessary and bumping up memory is not fixing the root cause:
"scripts": {
"run": "node --inspect --max-old-space-size=2048 index.js"
}
Bonus, async for loops
A tangent that I ran into while accomplishing this was asynch handling in for loops, which natively execute synchronously. This is an example solution that I came up with:
const forEachAsync = async (array, callback) => {
for (let index = 0; index < array.length; index++) {
await callback(array[index], index, array);
}
}
const UserService = {
getName: async (user) => `${user.firstName} ${user.lastName}`
}
const users = [
{ firstName: 'Babe', lastName: 'Ruth' },
{ firstName: 'Jimi', lastName: 'Hendrix' },
{ firstName: 'George', lastName: 'Brett' },
{ firstName: 'Winston', lastName: 'Churchill' },
];
const getUserNames = async () => {
await forEachAsync(users, async user => {
try {
let name = await UserService.getName(user);
console.log("Name: ", name);
} catch (err) {
console.error(err.message);
}
});
console.log("getUsers complete.");
};
(async () => {
try {
await getUserNames();
} catch (err) {
console.log(err.message);
}
console.log("Complete");
})();
Output:
Name: Babe Ruth
Name: Jimi Hendrix
Name: George Brett
Name: Winston Churchill
getUsers complete.
Complete
Bonus x2, Docker
Running your node app in docker:
node_docker.sh
#!/bin/bash
# pass arguments to the bash script and
# access via $1, $2 ... $N
docker run -v "$PWD":/usr/src/app \
-w /usr/src/app \
node:alpine \
sh -c "npm install && npm run" # append args here to pass to your script
chmod +x node_docker.sh
./node_docker.sh
Leave a Comment