Skip to content

[Bug]: ~5x memory regression (OOM) building a large graph on @rspack/core 2.1.x (fine on 2.0.x) #15021

Description

@utkarshkh

System Info

System:

  • OS: Ubuntu 22.04.5 LTS (Linux 6.8.0 azure)
  • CPU: (16) x64 AMD EPYC 7763
  • Memory: 62 GB
    Binaries:
  • Node: 22.18.0
  • npm: 10.9.3
    npmPackages:
  • @rspack/core: 2.1.5 (REGRESSION; last good 2.0.1)
  • @rsbuild/core: 2.1.4
  • storybook-builder-rsbuild: 2.1.0 and 3.3.4 (both affected)

Details

Regression range

Last good @rspack/core 2.0.1; broken in 2.1.x, still broken in the latest 2.1.5 (no 2.2.x yet).

What happens

A large app's Storybook production build (rsbuild + storybook-builder-rsbuild, mode: production):

  • @rspack/core 2.0.1 → ~9.7 GB peak RSS, succeeds
  • @rspack/core 2.1.5 → RSS climbs monotonically to ~50 GB and is OOM-killed

Same source, same config, only the @rspack/core version differs. Memory climbs during the module-build phase (Compilation:build_module_graph), before optimization, at a steady ~0.5 GB/s with no plateau — a per-module accumulation / leak signature that frees only at process teardown.

Bisection

  • A light story (tiny graph) builds at ~2 GB. A single heavy story (~7150 modules: @metaos/hub-sdk, many @fluentui/*, @apollo/client, graphql, relay-runtime, @griffel/core, plus first-party packages) OOMs at ~49 GB. So it scales with real graph size and one heavy entry is enough.
  • Effective cost is ≈3.7 MB peak RSS per module on 2.1.x vs ≈0.7 MB on 2.0.x (~5×), retained inside rspack core until the build ends.

Ruled OUT (none changed the ~49 GB OOM)

  • Storybook builder: Storybook 9 builder 2.1.0 AND Storybook 10 builder 3.3.4 both OOM.
  • Loader: the JS swc-loader (via @swc/core, both async transform and transformSync) and the Rust builtin:swc-loader all OOM.
  • SWC Wasm plugins (this build uses none).
  • experiments.parallelCodeSplitting: false.
  • optimization.minimize: false, concatenateModules: false, innerGraph/usedExports/providedExports/sideEffects: false, inlineConst/inlineExports: false.
  • Largest single modules (js-tiktoken 5 MB, @fluentui/react-icons chunks) — stubbing them did not change peak RSS.
  • resolve.extensionAlias / resolve.fallback.

⇒ The extra memory is retained inside @rspack/core 2.1.x core during build_module_graph, per module, independent of loader, optimizer pass, or config.

Profiling

RSPACK_PROFILE=ALL + an RSS timeline on a single heavy story:

  • The entire RSS climb is inside Compilation:build_module_graph.
  • The dominant trace span by wall time is the per-module transform hook (@1js/custom-swc-loader@swc/core), but that is only the per-module hook, not the memory cause (forcing transformSync and swapping to builtin:swc-loader both still OOM).
  • The prebuilt @rspack/binding is stripped, so the exact Rust allocation site can't be resolved on my side — a memory profile from a debug build (guidance welcome: RSPACK_PROFILE? heaptrack?) should pinpoint it.

Note on a minimal repro

I could not distill a public minimal repro (see "Steps to reproduce"). 13+ synthetic graphs — including one built from real public deps (20k @fluentui/react-icons + @fluentui/react-components + @apollo/client + relay-runtime + lodash-es) — all build with 2.1.5 using the memory of 2.0.1. The blow-up only reproduces on the real (partly first-party) graph. Happy to share a private repro or the 454 MB RSPACK_PROFILE trace with maintainers.

Reproduce link

No response

Reproduce Steps

There is no public minimal repro yet — the regression only reproduces on a large real graph (see "Note on a minimal repro" above). The reproduction/measurement methodology on the real build:

  1. Take a large app whose Storybook build produces a big module graph (thousands of first-party + Fluent UI / Apollo / Relay / griffel modules reached through a lazy import() hub).
  2. Build it in mode: production with @rspack/core 2.0.1 and record peak RSS (e.g. /usr/bin/time -v) → ~9.7 GB, succeeds.
  3. Change only @rspack/core to 2.1.5 (same rsbuild/storybook-builder-rsbuild, same config) and rebuild → RSS climbs monotonically (~0.5 GB/s, no plateau) to ~50 GB and is OOM-killed.
  4. Reduce to a single "heavy" story to confirm one entry (~7150 modules) is enough to OOM on 2.1.5 while a "light" story stays ~2 GB.
  5. Confirm it is core (not a loader/optimizer/config) by the ruled-out list above.

I can share a private reproduction repo and/or the RSPACK_PROFILE=ALL trace (454 MB) that shows the whole climb inside Compilation:build_module_graph. Guidance on capturing a Rust-core memory profile (heaptrack / a debug @rspack/binding) would let me pinpoint the exact allocation site.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions