Skip to content

perf(test): explicit GC between suites — peak heap 1.87→1.64 GB (−12%)#3545

Merged
PierreBrisorgueil merged 2 commits intomasterfrom
fix/memory-deeper-optimization
Apr 30, 2026
Merged

perf(test): explicit GC between suites — peak heap 1.87→1.64 GB (−12%)#3545
PierreBrisorgueil merged 2 commits intomasterfrom
fix/memory-deeper-optimization

Conversation

@PierreBrisorgueil
Copy link
Copy Markdown
Contributor

@PierreBrisorgueil PierreBrisorgueil commented Apr 30, 2026

Summary

  • Add --expose-gc to test:coverage NODE_OPTIONS so V8 exposes global.gc()
  • Add scripts/jest.gcReporter.cjs — minimal Jest reporter calling global.gc() after each test suite via onTestFileResult hook
  • Register reporter in jest.config.js alongside default reporter

Context

PR #3544 (Winston listener fix) cut peak heap from ~4.8–5.2 GB → ~1.87 GB by eliminating the uncaughtException listener leak from jest.resetModules() cycles. This PR pushes further.

With 99 suites in --runInBand, V8's heuristic GC retains old-generation objects (module registry snapshots, mock closures, Mongoose schema instances) longer than needed between suites. Explicit gc() after each suite releases them before the next suite loads its modules.

Measurements

Metric Before this PR After
Peak RSS (test:coverage) 1.87 GB 1.64 GB
Reduction −0.23 GB (−12.3%)
Tests 1226 pass 1226 pass

Target: ≤ 3 GB. Achieved.

Safety

  • global.gc is undefined for test:all, test:unit, test:integration (no --expose-gc in those scripts) → reporter is a no-op, zero behaviour change
  • .cjs extension because Jest reporters must be CommonJS (module.exports) even in an ESM project
  • Lint clean, no new test files needed (reporter has no logic path to cover beyond the gc call)

Follow-up items

  • restoreMocks: true would auto-restore jest.spyOn() closures per-test but breaks 8 integration test files that set spies in beforeAll expecting them to survive multiple tests (e.g. home.integration.tests.js, tasks.integration.tests.js). Deferred — needs per-file beforeEach migration.
  • trawl_node inherits from devkit; apply these changes via /update-stack to propagate gcReporter + --expose-gc. Trawl unit-only run is 3.59 GB (3030 tests), likely needs both this fix + the Winston fix upstream propagation.

Summary by CodeRabbit

  • Tests

    • Integrated custom reporter into test configuration to improve memory management during test execution.
    • Enhanced test coverage script with optimized garbage collection settings for better test performance.
  • Chores

    • Updated test configuration environment settings.

…erage run

Add --expose-gc to test:coverage NODE_OPTIONS and register a minimal Jest
reporter (scripts/jest.gcReporter.cjs) that calls global.gc() after each test
suite completes. With 99 suites in --runInBand, V8's heuristic GC trigger
retains old-generation objects longer than needed; explicit gc() calls
between suites allow V8 to collect module registry snapshots, mock closures,
and Mongoose schema objects accumulated by jest.resetModules() + unstable_mockModule
patterns before the next suite loads.

Peak RSS: 1.87 GB → 1.64 GB (−12.3%) with --expose-gc active.
The reporter is a no-op for test:all / test:unit / test:integration where
--expose-gc is absent (global.gc undefined), so no behaviour change on
those scripts.

Context: PR #3544 (Winston listener fix) cut peak from ~4.8–5.2 GB to ~1.87 GB.
This PR pushes further to 1.64 GB, well under the 3 GB target.

Verified: 99 suites / 1226 tests all pass.
Copilot AI review requested due to automatic review settings April 30, 2026 14:37
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 30, 2026

Warning

Rate limit exceeded

@PierreBrisorgueil has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 53 minutes and 44 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3aa5d3c5-b2d5-4697-abcc-343328c37fb0

📥 Commits

Reviewing files that changed from the base of the PR and between 802e6d5 and 41f2577.

📒 Files selected for processing (2)
  • package.json
  • scripts/jest.gcReporter.cjs

Walkthrough

Configuration and infrastructure changes are introduced to enable garbage collection management during test execution. A custom Jest reporter is added to invoke the garbage collector after each test file, with the Node runtime flag --expose-gc activated to expose the global.gc function.

Changes

Cohort / File(s) Summary
Test Configuration
jest.config.js, package.json
Jest configuration now registers a custom GC reporter; NODE_OPTIONS updated to expose garbage collector with --expose-gc flag.
Custom Reporter
scripts/jest.gcReporter.cjs
New Jest reporter module that calls global.gc() after each test file result when garbage collection is available.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: adding explicit garbage collection between test suites to reduce peak memory usage, with quantified performance metrics (1.87→1.64 GB, −12%).
Description check ✅ Passed The description covers all required template sections: Summary (what/why), Scope (modules impacted), Validation (test commands), Guardrails checks, and context. The author provides clear measurements, safety justification, and follow-up items.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/memory-deeper-optimization

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 0/1 reviews remaining, refill in 53 minutes and 44 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

@codacy-production
Copy link
Copy Markdown

codacy-production Bot commented Apr 30, 2026

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 0 complexity · 0 duplication

Metric Results
Complexity 0
Duplication 0

View in Codacy

AI Reviewer: first review requested successfully. AI can make mistakes. Always validate suggestions.

Run reviewer

TIP This summary will be updated as you push new changes.

Copy link
Copy Markdown

@codacy-production codacy-production Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

While this PR provides a notable 12% reduction in peak heap memory usage, it is currently in a broken state because the core implementation file, scripts/jest.gcReporter.cjs, was not included in the commit. This will cause Jest to fail with a 'module not found' error.

Additionally, there is a gap in the implementation: the --expose-gc flag is only applied to the test:coverage script. To maintain consistent memory performance, this flag should be extended to all scripts utilizing --runInBand (e.g., test:integration, test:all). Codacy reports the PR as up to standards, but it cannot be merged until the missing reporter file is added.

About this PR

  • There are no unit tests for the reporter logic itself. It is important to ensure the reporter handles cases where global.gc is undefined (e.g., during standard unit tests) without crashing.

Test suggestions

  • Ensure the test:coverage script correctly passes the --expose-gc flag to Node.js.
  • Verify that global.gc() is called after each test file result during coverage runs.
  • Verify that the reporter does not crash when global.gc is undefined in standard test runs.
Prompt proposal for missing tests
Consider implementing these tests if applicable:
1. Verify that global.gc() is called after each test file result during coverage runs.
2. Verify that the reporter does not crash when global.gc is undefined in standard test runs.

TIP Improve review quality by adding custom instructions
TIP How was this review? Give us feedback

Comment thread jest.config.js
Comment thread package.json Outdated
Comment thread jest.config.js
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@package.json`:
- Line 37: Update the "test:coverage" npm script in package.json to use escaped
double quotes for NODE_OPTIONS so it’s compatible with Windows cmd.exe: locate
the "test:coverage" script entry and replace the single-quoted NODE_OPTIONS
string with an escaped double-quoted one
(NODE_OPTIONS=\"--experimental-vm-modules --expose-gc\") to ensure
cross-platform execution in the "test:coverage" script.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 318f5351-96a8-4c92-93e4-8dd4887f9ffd

📥 Commits

Reviewing files that changed from the base of the PR and between 229ecf1 and 802e6d5.

📒 Files selected for processing (3)
  • jest.config.js
  • package.json
  • scripts/jest.gcReporter.cjs

Comment thread package.json Outdated
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 30, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 87.78%. Comparing base (229ecf1) to head (41f2577).
⚠️ Report is 1 commits behind head on master.

Additional details and impacted files
@@           Coverage Diff           @@
##           master    #3545   +/-   ##
=======================================
  Coverage   87.78%   87.78%           
=======================================
  Files         128      128           
  Lines        3587     3587           
  Branches     1052     1052           
=======================================
  Hits         3149     3149           
  Misses        347      347           
  Partials       91       91           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an explicit garbage-collection step between Jest test suites during coverage runs to reduce peak memory usage in long --runInBand executions.

Changes:

  • Add --expose-gc to test:coverage so global.gc() is available in that script.
  • Introduce a minimal custom Jest reporter that calls global.gc() after each test file/suite (no-op when unavailable).
  • Register the reporter in jest.config.js alongside the default reporter.

Reviewed changes

Copilot reviewed 2 out of 3 changed files in this pull request and generated 1 comment.

File Description
scripts/jest.gcReporter.cjs New Jest reporter that triggers global.gc() after each suite when available.
package.json Updates test:coverage NODE_OPTIONS to include --expose-gc.
jest.config.js Enables the custom GC reporter while retaining the default reporter.

Comment thread scripts/jest.gcReporter.cjs
- Replace single quotes around NODE_OPTIONS value with escaped double quotes
  for Windows/cross-env portability (cross-env README: single quotes not portable)
- Add JSDoc block to onTestFileResult per repo convention (every function
  needs @param + @returns)
@PierreBrisorgueil PierreBrisorgueil merged commit 96bf8df into master Apr 30, 2026
5 checks passed
@PierreBrisorgueil PierreBrisorgueil deleted the fix/memory-deeper-optimization branch April 30, 2026 14:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants