Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #1715 +/- ##
==========================================
- Coverage 92.47% 92.42% -0.06%
==========================================
Files 156 156
Lines 10602 10621 +19
==========================================
+ Hits 9804 9816 +12
- Misses 798 805 +7
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
This PR introduces dynamic memory snapshot support to address autoscaling limitations in environments with variable memory allocations (e.g., Kubernetes burstable QoS). It adds a Ratio type that allows the autoscaler to dynamically query available system memory rather than being locked to an initial baseline.
Changes:
- Introduced
Ratiotype for representing dynamic memory as a proportion of total system memory - Modified
SnapshotterandMemorySnapshotto accept eitherByteSize(fixed) orRatio(dynamic) for memory limits - Added logic to dynamically evaluate memory overload based on current available memory when using
Ratio
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
src/crawlee/_utils/byte_size.py |
Adds Ratio Pydantic model with validation for memory ratios (0.0 < value ≤ 1.0) |
src/crawlee/_autoscaling/snapshotter.py |
Updates max_memory_size parameter to accept ByteSize | Ratio and dynamically calculates memory limits when using Ratio |
src/crawlee/_autoscaling/_types.py |
Modifies MemorySnapshot.is_overloaded to dynamically query system memory when max_memory_size is a Ratio |
tests/unit/_autoscaling/test_snapshotter.py |
Adds comprehensive test simulating memory scale-up/scale-down scenarios with mocked memory info |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Mantisus
left a comment
There was a problem hiding this comment.
Looks good! I have a few comments below
| """The maximum memory that can be used by `AutoscaledPool`. | ||
|
|
||
| When of type `ByteSize` then it is used as fixed memory size. When of type `Ratio` then it allows for dynamic memory | ||
| scaling based on the available system memory. |
There was a problem hiding this comment.
I believe the available_memory_ratio docstring in Configuration should also mention this dynamic scaling behavior.
| class Ratio(BaseModel): | ||
| """Represents ratio of memory.""" | ||
|
|
||
| value: Annotated[float, Field(gt=0.0, le=1.0)] |
There was a problem hiding this comment.
I believe we could add gt=0.0 and le=1.0 constraints to available_memory_ratio in Configuration for consistency.
src/crawlee/_autoscaling/_types.py
Outdated
| # The snapshot overload is decided not when the snapshot was taken, but when `is_overload` property is | ||
| # accessed. This allows for dynamic memory scaling. The same memory snapshot that used to be overloaded in | ||
| # the past can become non-overloaded if the available memory was increased. | ||
| max_memory_size = ByteSize(int(get_memory_info().total_size.bytes * self.max_memory_size.value)) |
There was a problem hiding this comment.
I believe get_memory_info() should be wrapped in asyncio.to_thread since it uses psutil.
Also, I don't think historical snapshots should be affected by memory scaling - the overload was real at the moment the snapshot was taken. Once it's older than _SNAPSHOT_HISTORY, it won't influence the Snapshotter anyway. The 30-second inertia from old memory values seems reasonable to me.
There was a problem hiding this comment.
Good point. No need to be so "realtime-ish"
| self._evaluate_memory_load(event_data.memory_info.current_size, event_data.memory_info.created_at) | ||
|
|
||
| if isinstance(self._max_memory_size, Ratio): | ||
| max_memory_size = ByteSize(int(get_memory_info().total_size.bytes * self._max_memory_size.value)) |
There was a problem hiding this comment.
I think we could skip calling get_memory_info() when event_data.memory_info is MemoryInfo and just use memory_info.total_size.bytes instead.
6c11d73 to
3f27a14
Compare
|
|
||
| max_memory_size: ByteSize | ||
| """The maximum memory that can be used by `AutoscaledPool`.""" | ||
| max_memory_size: ByteSize | Ratio |
There was a problem hiding this comment.
In _snapshot_memory, we now always resolve Ratio to ByteSize, so I believe there should be only ByteSize. Also, don't forget to update the docstring.
| max_memory_size: ByteSize | Ratio | |
| max_memory_size: ByteSize |
| @@ -1,14 +1,22 @@ | |||
| from __future__ import annotations | |||
There was a problem hiding this comment.
I don't think the Ratio belongs here. A ratio is not a byte size - it's a proportion of memory. We should place Ration in the _autoscaling/_types.py where it is (only) used.
| _BYTES_PER_TB = _BYTES_PER_KB**4 | ||
|
|
||
|
|
||
| class Ratio(BaseModel): |
There was a problem hiding this comment.
This should probably be only a dataclass instead of a Pydantic model
| # This is just hypothetical case, that should not happen in practice. | ||
| # `LocalEvenManager` should always provide `MemoryInfo` in the event data. | ||
| # When running on Apify, `self._max_memory_size` is always `ByteSize`, not `Ratio`. |
| assert prev_time <= curr_time, f'Items at indices {i - 1} and {i} are not in chronological order' | ||
|
|
||
|
|
||
| _initial_memory_info = get_memory_info() |
There was a problem hiding this comment.
Can we use a fixture so that this does not run at import time?
|
Once you folks are done here, we need to make sure that this is also implemented in the JS port |
Description
Ratiotype to represent the maximum relative available memory of the system.Snapshotter.max_memory_sizeandMemorySnapshot.max_memory_sizewith eitherRatio(dynamic memory) orByteSize(fixed memory)Ratiois used, theMemorySnapshot.is_overloadedwill take into account the current available memory. (Previously, it would take into account only the initial available memory.)Top level usage in Crawlers:
Fixed memory
Dynamic memory
Issues
Testing
Checklist