Skip to content

Share CPU/memory autoscaling budget across multiple crawler instances in one process #3833

Description

@MrGeorgen

Feature

Expose a way to share CPU/memory snapshotting (or a combined concurrency/CPU budget) across multiple independent crawler instances (CheerioCrawler/HttpCrawler/etc.) running concurrently in the same Node process, so each instance's AutoscaledPool scales up/down based on combined load across all participating instances, not just its own isolated CPU snapshot.

Motivation

We run several independent crawler instances concurrently in one process (Promise.allSettled, one instance per target domain, each with its own isolated Configuration). We deliberately keep them separate rather than merging into one crawler: each domain needs its own maxRequestsPerMinute (some sites need a conservative rate to avoid bot-detection blocks, others tolerate a much higher rate — a single shared instance only exposes one global rate/concurrency, so merging would force a bad compromise). Running them in parallel this way is otherwise "free": one domain being rate-limited doesn't hold back another, so wall-clock time is roughly bounded by the slowest domain instead of the sum of all of them — no domain gets hit any faster than its own configured rate, so no additional block risk from the parallelism itself.

The problem: each instance's AutoscaledPool/Snapshotter only observes its own CPU usage and ramps concurrency up independently, unaware that N sibling instances in the same process are doing the same — collectively over-subscribing the CPU (Cheerio parsing is CPU-bound and blocking, cf. #282). Each pool looks "healthy" in isolation, but the sum across instances chokes the event loop, causing request timeouts.

We tried tuning each instance's maxRequestsPerMinute/maxConcurrency by hand so that, combined, they stay within our development machine's CPU headroom. That "worked" there, but it's tuning to one specific machine, not a real fix — the exact same config reintroduces timeouts on a weaker machine, because the actual constraint (total CPU across all instances) isn't something any single instance's AutoscaledPool knows about.

Ideal solution or implementation, and any additional constraints

An optional shared CPU/memory snapshotting mechanism (e.g. a shared Snapshotter, or a shared Configuration multiple crawler instances can opt into) so participating instances' AutoscaledPools base scale-up/scale-down decisions on total CPU/memory usage across all of them, not just their own. This needs to stay independent from each instance's own maxRequestsPerMinute/politeness limits — only the CPU/memory autoscaling signal should be shared, not the request rate itself.

Alternative solutions or implementations

  • Manually tuning each instance's limits by hand: only works for one specific machine, breaks on weaker hardware.
  • Splitting instances across OS processes: only pushes the ceiling since the number of usable cores is still finite, and reintroduces the same coordination problem one level up (now across processes instead of within one).

Other context

Related: #282 — Cheerio parsing being CPU-bound/blocking is what makes the combined-load choking noticeable in the first place.

Metadata

Metadata

Assignees

No one assigned

    Labels

    t-toolingIssues with this label are in the ownership of the tooling team.

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions