#3137·GitNexus

feat(analyze): adaptive worker concurrency under a memory budget + quieter rebuild gates + persisted needsFullRebuild marker (OOM ops feedback from a 25k-file repo)

Author: ChunxueLiCreated Sep 2, 2026Updated Sep 17, 2026

Context

Reindexing a large Java monorepo (~25.7k .java files, single winning-winex-opt repo) after a gitnexus upgrade triggered all three full-rebuild gates at once (schema fingerprint, analysis features, runner identity — expected and correct). The subsequent full re-analyze OOM'd twice on consecutive nights on a memory-constrained host (3 GB RAM + 17 GB swap, --max-old-space-size=8192):

[557175] 12990986 ms: Scavenge 7704.5 (8229.8) -> 7702.0 (8230.8) MB ... allocation failure
FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory

It ran for 3.7 hours before dying — ~2x slower than the last successful full analyze of the same repo (67 min on the previous build), thrashing in GC (mu = 0.02) for the last stretch. The worker-pool symptoms documented in README ("Analysis runs out of memory", #2649) also showed up in a follow-up run:

{"level":40,"workerIndex":2,"error":"Replacement worker did not report ready within 5000ms —
 likely crashed during top-of-script init ... repeated on a large repo? likely main-thread
 memory pressure — see the "Analysis runs out of memory" README section, #2649",
 "msg":"Worker 2 replacement failed to come online; dropping slot."}

This is a feature request born from ops reality, not a bug report — the gates themselves did the right thing. Three suggestions, in priority order.

1. Adaptive worker concurrency under a memory budget (--memory-budget or auto)

Today the worker count (and thus peak RSS) is effectively fixed, so a full analyze of a 25k-file repo needs a host-sized heap or it dies at hour 3 with nothing to show. Proposal:

  • Add an opt-in memory budget (e.g. --memory-budget-mb=4096 or env GITNEXUS_MEMORY_BUDGET_MB).
  • When set, derive worker count from the budget (start conservative: max(1, budget / perWorkerEstimate)), and drop worker slots instead of crashing when worker.replace fails on memory pressure — the pool already has the "dropping slot" concept, it just arrives too late (after the replacement crashed) and the run still dies later on the main thread.
  • Even without an explicit flag, a cheaper win: when a replacement worker fails to come online and the reason string contains "memory pressure", halve the live worker count once and log it, instead of retrying at the same width.

Trade-off to be explicit about: fewer workers = slower parse, but a run that finishes at 1.5x wall-clock beats a run that OOMs at 3.7h and leaves the repo unindexed.

2. Collapse the three rebuild gates into one diagnostic line

When schema fingerprint + analysis features + runner identity all change in one upgrade (the common case: "I pulled a new build"), the log prints three separate forcing a full ... lines. Operators then have to figure out whether they have three problems or one. Suggestion: detect the multi-hit and print a single summary:

full rebuild required — 3 gates tripped by this build change:
  - index schema changed (0061b2888f22 -> 6827b7d6b062)
  - analysis capabilities changed (missing: java.heritage-captures, ...)
  - analyzer runner identity changed
(all expected after upgrading the analyzer; one full re-analyze clears all three)

3. Persist a needsFullRebuild marker on failure so retries don't look mysterious

When a forced full analyze OOMs, the old (pre-gate) index is still on disk and the metadata still stamps the old fingerprint. The next cron run hits the same gates, tries the same full rebuild, and OOMs again — which is how we burned two consecutive nights before a human looked. Suggestion: on a failed forced rebuild, stamp something like meta.pendingFullRebuildReason = 'schema' | 'capabilities' | 'identity' (or reuse the existing gate evaluation, which already computes this) so that:

  • gitnexus status can say "index awaiting full rebuild since , reason: schema — last attempt failed after 3.7h" instead of looking like a fresh mystery every night;
  • ops tooling can detect "failed full rebuild" as a distinct state from "index missing".

Happy to turn any of these into a PR — #1 is the one we'd actually use daily (this host reindexes 23 repos nightly on 3 GB RAM).

Source: abhigyanpatwari/GitNexus