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.
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'sAutoscaledPoolscales 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 isolatedConfiguration). We deliberately keep them separate rather than merging into one crawler: each domain needs its ownmaxRequestsPerMinute(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/Snapshotteronly 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/maxConcurrencyby 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'sAutoscaledPoolknows about.Ideal solution or implementation, and any additional constraints
An optional shared CPU/memory snapshotting mechanism (e.g. a shared
Snapshotter, or a sharedConfigurationmultiple 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 ownmaxRequestsPerMinute/politeness limits — only the CPU/memory autoscaling signal should be shared, not the request rate itself.Alternative solutions or implementations
Other context
Related: #282 — Cheerio parsing being CPU-bound/blocking is what makes the combined-load choking noticeable in the first place.