Why Your CI Build Runs Out of Memory and How to Fix It with Node.js Profiling

During CI builds of front‑end projects, developers often encounter out‑of‑memory errors or silent process termination, but by measuring Node.js memory and CPU usage, adjusting the max‑old‑space‑size flag, employing profiling tools, and optimizing webpack plugins, they can identify and mitigate resource‑consumption bottlenecks.

Huolala Tech
Huolala Tech
Huolala Tech
Why Your CI Build Runs Out of Memory and How to Fix It with Node.js Profiling

When packaging a front‑end project in a CI environment you may see errors such as out‑of‑memory (OOM) or a build process that exits without any error output.

<--- Last few GCs --->

[1:0x63b6120]   122046 ms: Mark-sweep (reduce) 2003.3 (2005.1) -> 2003.2 (2005.1) MB, 4.1 / 0.5 ms  (+ 0.1 ms in 1 steps since start of marking, biggest step 0.1 ms, walltime since start of marking 47 ms) (average mu = 0.999, current mu = 0.999) external

<--- JS stacktrace --->

FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory

Another possible symptom is a sudden termination with exit code 137:

Killed
error Command failed with exit code 137.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

Setting the max-old-space-size flag can solve these problems, but understanding why it works requires deeper analysis of resource consumption during the build.

Basic Knowledge

Node.js provides several APIs for measuring performance data. For front‑end build processes we mainly care about memory and CPU metrics.

Memory Monitoring

Memory data can be obtained easily using the built‑in process and os modules.

import { freemem, totalmem } from 'os';

const { rss, heapUsed, heapTotal } = process.memoryUsage();

const sysFree = freemem(); // system free memory
const sysTotal = totalmem(); // system total memory

From these values we can calculate various usage rates:

heapUsed / heapTotal; // heap memory usage ratio
rss / sysTotal; // process memory usage ratio
1 - sysFree / sysTotal; // system memory usage ratio

The heap memory, managed by the V8 engine, stores JavaScript objects, variables, and functions. Most memory consumption in a Node.js application comes from the heap, which can be enlarged via the max-old-space-size flag.

Profile

V8’s profiler can capture heap snapshots for leak detection. The snapshot can be saved and opened in Chrome DevTools:

import { Session } from 'inspector';

const session = new Session();
session.connect();

async function dumpProfile() {
  session.on('HeapProfiler.addHeapSnapshotChunk', (m) => {
    writeFileSync('profile.heapsnapshot', m.params.chunk);
  });
  await session.post('HeapProfiler.takeHeapSnapshot', null);
}

For detailed analysis of the snapshot, refer to the V8 documentation.

CPU Analysis

The process.cpuUsage method returns user and system CPU time, while process.hrtime.bigint provides a high‑resolution timestamp. Combining them yields CPU utilization:

const startTime = process.hrtime.bigint();
const startUsage = process.cpuUsage();

doSomething();

const endTime = process.hrtime.bigint();
const endUsage = process.cpuUsage(startUsage);

const duration = Number(endTime - startTime) / 1000; // ms
(endUsage.user + endUsage.system) / duration; // cpu utilization

Effect of max-old-space-size

Now we revisit the OOM problem and examine how adjusting max-old-space-size influences the Node.js process.

The maximum heap size can be obtained via the V8 API:

import { getHeapStatistics } from 'v8';
Math.floor(getHeapStatistics().heap_size_limit / 1024 / 1024);

On a 4 GB Node.js v16 runtime this script reports a limit of about 2015 MB. A simple script that continuously allocates 50 MB buffers demonstrates memory growth:

// adapted from https://github.com/mcollina/climem/blob/master/app.js
const array = [];
setInterval(() => {
  array.push(Buffer.alloc(1024 * 1024 * 50).toString()); // 50 MB
}, 3000);

The memory usage chart below shows the process hitting OOM after ~122 seconds, with heap usage approaching the heap_size_limit and only ~700 MB free memory remaining.

Increasing the flag to max-old-space-size=3584 delays OOM until ~220 seconds, demonstrating that a larger heap allows the build to consume more memory safely.

Thus, raising max-old-space-size can effectively reduce OOM occurrences during builds.

Build Analysis

Using create-react-app as a sample project, we apply the following techniques.

Webpack ProfilingPlugin

The ProfilingPlugin generates a profile file that can be inspected in Chrome DevTools:

{
  plugins: [
    // ...
    new ProfilingPlugin({
      outputPath: join(__dirname, 'profile', `profile.json`),
    }),
  ];
}

Import the resulting profile.json into the DevTools Performance panel to view the build timeline.

The following table lists the plugins that consume the most time:

Do not use ProfilingPlugin in production projects; it consumes many resources and the generated profile can be so large that importing it into Chrome DevTools may crash.

Memory Monitoring

Based on the earlier basics, a custom memory‑watch plugin can be written by tapping into Webpack’s compiler hooks:

// Collect data at intervals
async function collectMemoryUsage() {}

class MemWatchPlugin {
  apply(compiler) {
    // before build starts
    compiler.hooks.beforeRun.tap(pluginName, collectMemoryUsage);
    // after build finishes
    compiler.hooks.done.tap(pluginName, saveMemoryUsageData);
    // on build failure
    compiler.hooks.failed.tap(pluginName, saveMemoryUsageData);
  }
}

Visualizing the collected data yields the memory‑usage trend chart below:

Analysis

By merging the memory trend with the plugin‑time chart, we obtain a combined view that highlights which build phases consume the most resources.

Mapping the rapid growth periods (stages 1 and 2) to specific plugins shows that memory‑intensive tasks, such as JavaScript minification, dominate resource usage. After code compression the memory usage drops slightly, and the build finishes shortly thereafter.

max-old-space-size Not Effective Scenarios

If the project includes heavy packages like monaco-editor, memory consumption spikes dramatically during the build, leaving very little free memory. In such cases increasing max-old-space-size has limited effect; the better solution is to mark those packages as externals so they are not bundled.

Summary

This article introduced tools for measuring performance metrics, demonstrated a concrete front‑end build case, and analyzed resource consumption throughout the build pipeline. Armed with these insights, developers can better understand and optimize their build processes, reducing OOM incidents and improving overall efficiency.

References

[1]

OOM: https://en.wikipedia.org/wiki/Out_of_memory [2] Resource consumption: https://webpack.js.org/guides/build-performance/ [3] BigInt: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/BigInt [4] Node.js CLI docs: https://nodejs.org/dist/latest-v18.x/docs/api/cli.html#--max-old-space-sizesize-in-megabytes [5] ProfilingPlugin: https://webpack.js.org/plugins/profiling-plugin/#root [6] Crash example: https://devblog.kogan.com/blog/optimising-webpack-build-performance [7] monaco-editor: https://github.com/microsoft/monaco-editor

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Node.jsmemory profilingCI buildmax-old-space-size
Huolala Tech
Written by

Huolala Tech

Technology reshapes logistics

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.