feat(node): bare-metal worker autoscaler - #542
Conversation
Spawns and drains worker processes to track queue depth on hosts without Kubernetes, mirroring Python's AutoscaleController: HPA depth and utilisation signals, per-direction stabilisation windows, a tolerance band, and crash replacement.
|
Warning Review limit reached
Next review available in: 37 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (6)
📝 WalkthroughWalkthroughChangesNode bare-metal autoscaler
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant Autoscaler
participant Queue
participant WorkerProcessManager
Autoscaler->>Queue: Read queue statistics
Queue-->>Autoscaler: Return pending and running counts
Autoscaler->>Autoscaler: Compute and stabilize desired worker count
Autoscaler->>WorkerProcessManager: Spawn or drain workers
WorkerProcessManager-->>Autoscaler: Update worker pool state
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@sdks/node/src/autoscale/controller.ts`:
- Around line 159-218: Track the currently executing tick promise in the
autoscaler, and have stop() await that promise after disabling scheduling and
before calling manager.shutdown(). Ensure the tick promise is cleared when it
settles, while preserving the existing non-overlapping schedule behavior and
making repeated stop() calls safe.
- Around line 220-257: Update applyWindows to record every tick’s raw desired
recommendation in the relevant stabilization history, including ticks where
desired equals current, so a transient low recommendation is compared with the
prior stable recommendation. Preserve the existing min/max aggregation semantics
for scale-up and scale-down windows, and ensure a failed gatherMetrics reading
cannot immediately reduce a pool above minWorkers.
In `@sdks/node/src/autoscale/processManager.ts`:
- Around line 80-118: Move the child.once("error", ...) registration in
spawnWorker() to immediately after spawn(), before checking child.pid or
throwing for an undefined pid. Keep the existing error logging and forget(pid,
record) behavior, while ensuring the listener safely handles failed spawns where
no pid is available.
In `@sdks/node/test/observability/autoscale.test.ts`:
- Around line 205-214: The autoscaler regression test currently covers only a
pool already at minWorkers. Extend the test around autoscaler and metricsSource
with a case that seeds more workers than the configured minimum (for example, 5
versus 1), simulates one metrics-read failure, and asserts the decision
preserves the current pool size without spawning or terminating workers.
🪄 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: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 8ecdb1b7-db1c-4b07-a876-0e21136f620b
📒 Files selected for processing (17)
CHANGELOG.mddocs/content/docs/node/api-reference/cli.mdxdocs/content/docs/node/getting-started/capabilities.mdxdocs/content/docs/node/guides/operations/autoscaling.mdxdocs/content/docs/node/guides/operations/cli.mdxdocs/content/docs/node/guides/operations/index.mdxdocs/content/docs/node/guides/operations/meta.jsonsdks/node/src/autoscale/config.tssdks/node/src/autoscale/controller.tssdks/node/src/autoscale/index.tssdks/node/src/autoscale/processManager.tssdks/node/src/cli/commands/autoscale.tssdks/node/src/cli/commands/index.tssdks/node/src/cli/index.tssdks/node/src/index.tssdks/node/test/integrations/autoscale.test.tssdks/node/test/observability/autoscale.test.ts
A spawn that fails asynchronously emits 'error' with no pid; registering the listener after the throw left it unhandled, taking the autoscaler down.
stop() cleared the timer and drained immediately, so a tick still awaiting its metrics read could spawn afterwards and leak a detached worker.
Only direction changes were buffered, so the first dip after a stable stretch had nothing to smooth against and took effect at once — including a dip caused by a failed metrics read.
Closes #518.
Node was the only SDK without a local autoscaler — Python has the process-based
AutoscaleController+ CLI, Java has the thread-poolAutoscaler, and Node had only the KEDAserveScaler. This adds the bare-metal one.What it does
serveAutoscaler(queue, { app })runs a control loop that spawns and drains worker processes to track queue depth, for hosts without Kubernetes. The formula mirrors Python's (and the Kubernetes HPA):with per-direction stabilisation windows (scale-up immediate, scale-down 5 min), a 10% tolerance band, an overload override, and crash replacement up to
minWorkers.Design notes
node --input-type=module -e), importing the user's app module and driving it through the publicQueueAPI. Nothing has to locate adist/entry, so it behaves the same from source, from a bundle, and from an install. Paths arrive viaargv, never interpolated into the source.concurrencyPerWorkeris applied, not just declared. Python'sthreads_per_workeris an operator promise the controller can't verify; here the autoscaler spawns the workers, so it sets their concurrency and the utilisation signal's capacity is right by construction.queuesset, the depth signal reads only those queues rather than global stats.Verification
mastertoo (viteisn't installed in thedashboard/workspace locally) and are untouched by this change.typecheck,biome check, and the docstypecheck/lint/check:parityare all clean.Docs
New
node/guides/operations/autoscalingguide, plus entries in the operations index, both CLI pages, and the capabilities table.Summary by CodeRabbit
New Features
autoscaleCLI command with configurable worker limits and scaling options.Documentation
Tests