You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 51efb37
Browse filesBrowse the repository at this point in the historyBrowse files
feat(autoscaling)!: Add ConcurrencySystem to share one concurrency budget between crawlers (#2322)
### Description
Load monitoring and concurrency scaling move from `AutoscaledPool` into
a new `ConcurrencySystem`. Pass one system to several crawlers through
the new `concurrency_system` option to cap their combined concurrency.
The autoscaling classes are now public in `crawlee.autoscaling`.
Autoscaling logs move to DEBUG, and `max_tasks_per_minute` now counts
the tasks started over the last 60 seconds. Breaking changes are in the
v2 upgrade guide.
### Issues
- Closes: #2133
- Closes: #1591
- Closes: #705
*✍️ Drafted by Claude Code*
Copy file name to clipboardExpand all lines: docs/guides/architecture_overview.mdx
+2-2Lines changed: 2 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -351,12 +351,12 @@ The <ApiLink to="class/EventManager">`EventManager`</ApiLink> is responsible for
351
351
Crawlee provides several implementations of the event manager:
352
352
353
353
- <ApiLinkto="class/EventManager">`EventManager`</ApiLink> is the base class for event management in Crawlee.
354
-
- <ApiLinkto="class/LocalEventManager">`LocalEventManager`</ApiLink> extends the base event manager for local environments by automatically emitting `SYSTEM_INFO` events at regular intervals. This provides real-time system metrics including CPU usage and memory consumption, which are essential for internal components like the <ApiLinkto="class/Snapshotter">`Snapshotter`</ApiLink> and <ApiLinkto="class/AutoscaledPool">`AutoscaledPool`</ApiLink>.
354
+
- <ApiLinkto="class/LocalEventManager">`LocalEventManager`</ApiLink> extends the base event manager for local environments by automatically emitting `SYSTEM_INFO` events at regular intervals. This provides real-time system metrics including CPU usage and memory consumption, which are essential for internal components like the <ApiLinkto="class/Snapshotter">`Snapshotter`</ApiLink> and <ApiLinkto="class/ConcurrencySystem">`ConcurrencySystem`</ApiLink>.
355
355
-[`ApifyEventManager`](https://docs.apify.com/sdk/python/reference/class/ApifyEventManager) - Manages events on the [Apify platform](https://apify.com/) (cloud-based). It is implemented in the [Apify SDK](https://docs.apify.com/sdk/python/).
356
356
357
357
:::info
358
358
359
-
You can learn more about <ApiLinkto="class/Snapshotter">`Snapshotter`</ApiLink> and <ApiLinkto="class/AutoscaledPool">`AutoscaledPool`</ApiLink> and their configuration in the [Scaling crawlers guide](./scaling-crawlers).
359
+
You can learn more about <ApiLinkto="class/Snapshotter">`Snapshotter`</ApiLink> and <ApiLinkto="class/ConcurrencySystem">`ConcurrencySystem`</ApiLink> and their configuration in the [Scaling crawlers guide](./scaling-crawlers).
As we build our crawler, we may want to control how many tasks it performs at any given time. In other words, how many requests it makes to the web we are trying to scrape. Crawlee offers several options to fine-tune the number of parallel tasks, limit the number of requests per minute, and optimize scaling based on available system resources.
14
15
@@ -20,7 +21,7 @@ All of these options are available across all crawlers provided by Crawlee. In t
20
21
21
22
## Max tasks per minute
22
23
23
-
The `max_tasks_per_minute` setting in <ApiLinkto="class/ConcurrencySettings">`ConcurrencySettings`</ApiLink> controls how many total tasks the crawler can process per minute. It ensures that tasks are spread evenly throughout the minute, preventing a sudden burst at the `max_concurrency` limit followed by idle time. By default, this is set to `Infinity`, meaning the crawler can run at full speed, limited only by `max_concurrency`. Use this option if you want to throttle your crawler to avoid overwhelming the target website with continuous requests.
24
+
The `max_tasks_per_minute` setting in <ApiLinkto="class/ConcurrencySettings">`ConcurrencySettings`</ApiLink> controls how many total tasks the crawler can process per minute. The tasks started over the last 60 seconds are counted, so the crawler may spend the whole limit early in the minute, as fast as its concurrency allows. It then waits until the oldest of those starts ages out of the window. By default, this is set to `Infinity`, meaning the crawler can run at full speed, limited only by `max_concurrency`. Use this option if you want to throttle your crawler to avoid overwhelming the target website with continuous requests.
@@ -44,9 +45,15 @@ The `desired_concurrency` option in the <ApiLink to="class/ConcurrencySettings">
44
45
{MinAndMaxConcurrencyExample}
45
46
</RunnableCodeBlock>
46
47
47
-
## Autoscaled pool
48
+
## Concurrency system
48
49
49
-
The <ApiLinkto="class/AutoscaledPool">`AutoscaledPool`</ApiLink> manages a pool of asynchronous, resource-intensive tasks that run in parallel. It keeps `min_concurrency` tasks running even while the system is overloaded, and starts additional tasks only when there is enough free CPU and memory. To monitor system resources, it leverages the <ApiLinkto="class/Snapshotter">`Snapshotter`</ApiLink> and <ApiLinkto="class/SystemStatus">`SystemStatus`</ApiLink> classes. If any task raises an exception, the error is propagated, and the pool is stopped. Every crawler uses an <ApiLinkto="class/AutoscaledPool">`AutoscaledPool`</ApiLink> under the hood.
50
+
Every crawler runs its requests through an <ApiLinkto="class/AutoscaledPool">`AutoscaledPool`</ApiLink>, and the pool asks a <ApiLinkto="class/ConcurrencySystem">`ConcurrencySystem`</ApiLink> whether there is room for one more request. The system is what keeps `min_concurrency` requests running even while the machine is overloaded, starts further ones only when there is enough free CPU and memory, and scales the budget up and down as the load changes. To monitor system resources, it leverages the <ApiLinkto="class/Snapshotter">`Snapshotter`</ApiLink> and <ApiLinkto="class/SystemStatus">`SystemStatus`</ApiLink> classes. If any task raises an exception, the error is propagated and the pool is stopped.
51
+
52
+
The crawler builds its own system from the `ConcurrencySettings` described above. Build one yourself and pass it as `concurrency_system` to go beyond those settings, or to share a single budget between several crawlers that would otherwise each scale against the whole machine and oversubscribe it. The budget goes to whoever asks for it first, so a crawler that joins a saturated system late may have to wait. Browser crawlers default to a lower `desired_concurrency` than the rest, and a supplied system replaces that default too, so set it in the `ConcurrencySettings` you build the system from. Holding the instance also lets you retune `min_concurrency` and `max_concurrency` while the crawl runs. The lifecycle of such a system belongs to you, so keep it open until every crawler using it has finished.
Copy file name to clipboardExpand all lines: docs/guides/scrapy_migration.mdx
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -195,7 +195,7 @@ The default (unnamed) <ApiLink to="class/Dataset">`Dataset`</ApiLink> stays on d
195
195
196
196
## Concurrency and throttling
197
197
198
-
Both Scrapy and Crawlee have settings to influence the scraping throughput, but they differ in their approach. Crawlee reads <ApiLinkto="class/ConcurrencySettings">`ConcurrencySettings`</ApiLink> and scales concurrency automatically based on system resources, as described in the [Scaling crawlers guide](./scaling-crawlers). Use <ApiLinkto="class/ConcurrencySettings#__init__">`max_tasks_per_minute`</ApiLink> to cap the request rate. It's close to `DOWNLOAD_DELAY`, but it limits the whole pool rather than adding a fixed delay per domain.
198
+
Both Scrapy and Crawlee have settings to influence the scraping throughput, but they differ in their approach. Crawlee reads <ApiLinkto="class/ConcurrencySettings">`ConcurrencySettings`</ApiLink> and scales concurrency automatically based on system resources, as described in the [Scaling crawlers guide](./scaling-crawlers). Use <ApiLinkto="class/ConcurrencySettings#__init__">`max_tasks_per_minute`</ApiLink> to cap the request rate. It's close to `DOWNLOAD_DELAY`, but it limits the whole pool rather than adding a fixed delay per domain. Requests aren't spaced out either. They start as fast as the concurrency allows until the cap for the last 60 seconds is reached, and then wait for that window to move on.
Copy file name to clipboardExpand all lines: docs/upgrading/upgrading_to_v2.md
+64Lines changed: 64 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -103,3 +103,67 @@ The `id` argument of `BasicCrawler` and its subclasses is now a `str`, so pass a
103
103
The statistics a crawler creates by default now use the crawler's `id`. Crawlers that share an `id` therefore share both the state returned by `use_state` and the persisted statistics. In v1, they shared only the state.
104
104
105
105
A crawler without an `id` gets its creation index, such as `'0'`, and crawlers with an explicit `id` now count towards that index too. If you resume a v1 run with `purge_on_start=False` and mix both kinds of crawlers, a generated `id` can differ from the one v1 assigned, and the crawler won't find the state that `use_state` stored under the old one.
106
+
107
+
## Concurrency is managed by `ConcurrencySystem`
108
+
109
+
Load monitoring and concurrency scaling moved from `AutoscaledPool` to the new `ConcurrencySystem`. A crawler builds a default system from its `concurrency_settings` for each run, so a crawler configured through `concurrency_settings` works as before. To cap the combined concurrency of several crawlers, pass one shared `ConcurrencySystem` to all of them through the new `concurrency_system` option.
110
+
111
+
The autoscaling classes now live in the public `crawlee.autoscaling` package, which replaces the private `crawlee._autoscaling`. Import `AutoscaledPool`, `ConcurrencySystem`, `ConcurrencyConsumer`, `SystemInfo` and `LoadRatioInfo` from there. `Snapshotter` and `SystemStatus` stay internal.
112
+
113
+
`AutoscaledPool` now only runs the task loop. If you create a pool yourself, note that:
114
+
115
+
-`system_status` and `concurrency_settings` are replaced by `concurrency_system`, which has to be active when the pool runs, and `consumer`, which identifies the pool to the system.
116
+
-`run_task_function` receives the id the task is booked under.
117
+
-`current_concurrency` comes from the concurrency system, so a shared system may count the tasks of other pools too. The pool's own tasks are counted by the new `own_concurrency`.
118
+
119
+
For example:
120
+
121
+
```python
122
+
# v1
123
+
from crawlee._autoscaling import AutoscaledPool, Snapshotter, SystemStatus
124
+
125
+
asyncwith Snapshotter.from_config() as snapshotter:
asyncwith ConcurrencySystem(settings) as concurrency_system:
141
+
pool = AutoscaledPool(
142
+
concurrency_system=concurrency_system,
143
+
consumer=ConcurrencyConsumer(id='my-pool'),
144
+
run_task_function=run_task, # Now called with the task id.
145
+
is_task_ready_function=is_task_ready,
146
+
is_finished_function=is_finished,
147
+
)
148
+
await pool.run()
149
+
```
150
+
151
+
## `max_tasks_per_minute` counts the last minute
152
+
153
+
`ConcurrencySettings.max_tasks_per_minute` now caps the tasks started over the last 60 seconds, the same as Crawlee JS. In v1, the pool spaced task starts evenly, `60 / max_tasks_per_minute` seconds apart. In v2, tasks start as fast as the concurrency allows until the cap is reached. The next ones wait until the oldest starts age out of the window. The rate over a minute stays the same, but tasks start in bursts. For details, see [Max tasks per minute](../guides/scaling-crawlers#max-tasks-per-minute).
154
+
155
+
The value has to be a whole number of at least 1, or infinity, which is the default. A fractional value, such as `0.5` or `7.5`, now raises `ValueError`.
156
+
157
+
## Autoscaling logs changed
158
+
159
+
The periodic log of the autoscaling state and the `Waiting for remaining tasks to finish` message are now logged at DEBUG instead of INFO. The crawler's status message at INFO still reports the desired concurrency.
160
+
161
+
The state log also has a new format. Instead of raw ratios for every resource, it names only the overloaded ones, with the share of time each spent over its threshold and its limit:
162
+
163
+
```text
164
+
current concurrency 3, desired 5, overloaded: cpu 58% of the time (limit 40%)
165
+
```
166
+
167
+
To read the concurrency values in code, use `crawler.concurrency_system`.
168
+
169
+
The autoscaling loggers moved together with the package. `crawlee._autoscaling.autoscaled_pool`, `crawlee._autoscaling.snapshotter` and `crawlee._autoscaling.system_status` are now `crawlee.autoscaling._autoscaled_pool`, `crawlee.autoscaling._snapshotter` and `crawlee.autoscaling._system_status`. The state log, along with the messages about scaling and free capacity, now comes from the new `crawlee.autoscaling._concurrency_system` logger. To configure all of them at once, use the `crawlee.autoscaling` logger.
0 commit comments