feat(compiler): memory-aware concurrency limit#3764
Conversation
8fbc440 to
6a3714b
Compare
|
Claude finished @RafaelGranza's task in 3m 47s —— View job PR Review: memory-aware concurrency limit
Reviewed for correctness, the cgroup/container divergence, the fail-fast path, and the flag wiring. This is a clean, well-tested PR — no blockers or correctness bugs. One inline nit and a few notes below. BlockerNone. ImportantNone. Nit
Things I checked that are correct
One heads-up (not a bug)On a many-core but memory-modest host (e.g. 16 CPU / 16 GB, defaults), derived concurrency drops from the old |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #3764 +/- ##
==========================================
+ Coverage 75.24% 75.32% +0.07%
==========================================
Files 439 440 +1
Lines 39397 39442 +45
==========================================
+ Hits 29645 29709 +64
+ Misses 7671 7655 -16
+ Partials 2081 2078 -3 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
6a3714b to
d8bdcfb
Compare
|
Claude finished @RafaelGranza's task in 2m 58s —— View job PR Review: memory-aware concurrency limit
This PR is clean — no blockers, no correctness bugs. The change is well-scoped, deterministically tested, and the prior review's log nit has been resolved. BlockerNone. ImportantNone. Things I re-verified on this revision
Nit (non-blocking)
Heads-up for release notes (not a bug)On a many-core / memory-modest host (e.g. 16 CPU / 16 GB, defaults), derived concurrency drops from the old |
d8bdcfb to
ff2d77b
Compare
|
Claude finished @RafaelGranza's task in 57s —— View job PR Review: memory-aware concurrency limit
This revision is clean — no blockers, no correctness bugs. Builds and vets clean locally ( BlockerNone. ImportantNone. Re-verified on this revision
Nit (non-blocking)
Heads-up for release notes (not a bug)On a many-core / memory-modest host (e.g. 16 CPU / 16 GB, defaults), derived concurrency drops from the old |
ff2d77b to
6c2bd83
Compare
|
Claude finished @RafaelGranza's task in 3m 4s —— View job PR Review: memory-aware concurrency limit (re-review after sync)
This revision is clean — no blockers, no correctness bugs. The notable change in this push is that the fail-fast path was replaced with a floor of 1 ( BlockerNone. ImportantNone. Verified on this revision
Nit (non-blocking)
Heads-up for release notes (not a bug)On a many-core / memory-modest host (e.g. 16 CPU / 16 GB, defaults), derived concurrency drops from the old |
6c2bd83 to
22bfa47
Compare
|
Claude finished @RafaelGranza's task in 54s —— View job PR Review: memory-aware concurrency limit (re-review on sync)
This revision is clean — no blockers, no correctness bugs. The change is well-scoped and deterministically tested. BlockerNone. ImportantNone. Verified on this revision
Nit (non-blocking)
Heads-up for release notes (not a bug)On a many-core / memory-modest host (e.g. 16 CPU / 16 GB, defaults), derived concurrency drops from the old |
rodrodros
left a comment
There was a problem hiding this comment.
Rafael the 0 as a way to set is default is not a good solution because 0 can be a desired value in itself. A node set with 0,0 values can me a node that will reject any compilation requests.
Since Cobra allow us to have some expression, can't the defaults be triggered at runtime and CLI print the default values? I don't know if this is a good approach.
The other approach is to allow other values, such as "default" or "dynamic" which are non-numbers and if set, then the calculation is done
|
@rodrodros |
22bfa47 to
c3a7e8e
Compare
|
Claude finished @RafaelGranza's task in 3m 52s —— View job PR Re-review:
|
c3a7e8e to
221e7c6
Compare
|
I'll analyze this and get back to you. |
daace80 to
33ce1f5
Compare
|
Claude finished @RafaelGranza's task in 3m 22s —— View job PR Re-review (sync)
Verdict: clean — no blockers, no correctness bugs. @rodrodros's
|
33ce1f5 to
1ef5689
Compare
|
Claude finished @RafaelGranza's task in 1m 22s —— View job PR Re-review (sync)
Verdict: clean — no blockers, no correctness bugs. Verified correct on this revision
Nits (non-blocking)
Heads-up for release notes (not a bug)Default derived concurrency on a many-core / memory-modest host (e.g. 16 CPU / 16 GB) is |
|
@rodrodros I refactored the code. I decided to parse the flags at the cmd level because otherwise the throttled compiler, which was error-free, would suddenly have to carry parsing errors. On top of that, parsing a CLI flag shouldn't live inside |
| type ThrottledCompilerSettings struct { | ||
| maxConcurrency uint64 | ||
| queueSize uint64 | ||
| } |
There was a problem hiding this comment.
You are creating a struct (also public) to handle just two values, which is also used only one? Couldn't this function return the two appointed values directly?
There was a problem hiding this comment.
It was to keep these two tied together, so that resolving the setting would actually return a setting.
I wasn't aware structs could be lowercase (private), which it should be.
I'm not sure if it fits as over-engineering, but it is not needed.
There was a problem hiding this comment.
i´ll be removing it anyway
| } | ||
| } | ||
|
|
||
| func NewThrottledCompiler( |
There was a problem hiding this comment.
What's the point of NewThrottledCompiler now? It seems to me there is no longer a point for this function?
There was a problem hiding this comment.
I like the test that NewThrottledCompiler exercises (node/throttled_compiler_test.go).
It could also be exercised by the new functions, but we would require us to set them public and passing a compiler.Compiler, creating a messier signature,.
More than that, i think it is good having a public log-free constructor.
1ef5689 to
5d65448
Compare
| maxConcurrency uint64, | ||
| availableMemory uint64, | ||
| nodeMemoryReserve uint64, | ||
| maxMemoryPerCompilation uint64, |
There was a problem hiding this comment.
I considered computing maxConcurrency internally, but it was handy for testing its behavior.
5d65448 to
56e11cb
Compare
56e11cb to
63a269b
Compare
|
Claude finished @RafaelGranza's task in 3m 54s —— View job PR Re-review (sync): cmd-level
|

Auto-size Sierra compilation concurrency from available memory
What
Today the number of concurrent Sierra→CASM compilations defaults to
GOMAXPROCS, which ignores memory. A burst of large compilations can exhaust RAM.This PR derives the limit from available memory and CPU count.
Formula (all values in MB):
The limit is always at least 1: a memory-tight node compiles one at a time rather than refusing to start.
Behavior change (heads-up for operators)
The default concurrency is no longer always
GOMAXPROCS. On a many-core but memory-modest host it drops: e.g. 16 CPU / 16 GB with defaults goes from16to(16-4)/4 = 3. Intended, but nodes with heavy P2P orgetCompiledCasmload will see less compile parallelism. Raise--max-concurrent-compilations(used as-is) or--node-memory-reserveto tune.Flags
--max-concurrent-compilationsunsetunset(flag not provided) = derive from memory + CPUs. A provided value is used as-is (an explicit0disables compilations).--max-compilation-queueunsetunset= twice the resulting concurrency. A provided value is used as-is.--node-memory-reserve4096--max-compilation-memory4096Compilation-sizing flags are parsed as unsigned integers by the CLI, so a non-numeric or negative value is rejected at flag-parse time. Whether a flag was provided (across CLI, env, or YAML) is what selects derive-vs-explicit, so an explicit
0is a valid, distinct value.Why these libraries
Two needs: read total RAM, and read the container memory limit. Choice:
pbnjay/memoryautomemlimitGOMEMLIMIT, pure Go, reads cgroup v1/v2.Both were already indirect dependencies; this only promotes them to direct.
automemlimit's only dependency ispbnjay/memory, so the pair adds no new transitive dependency.gopsutilwas considered and dropped: it does not read the cgroup limit (same blind spot aspbnjay), and it would be an extra dependency on top ofpbnjay(whichautomemlimitpulls anyway).Container awareness
A cgroup is the Linux kernel feature that caps how much memory a process group may use; it is how Docker and Kubernetes enforce memory limits. A process can sit on a 64 GiB host but be capped to 4 GiB by its cgroup, and the kernel kills it (OOM) if it goes over.
Reading total system memory (
sysinfo//proc/meminfo) returns the host RAM and ignores cgroups, so inside a memory-limited container it over-reports.AvailableMemoryMB()instead returnsmin(host RAM, cgroup limit), so it respectsdocker run --memory=..and Kubernetes limits.Margin default
--node-memory-reservedefaults to 4 GiB, a conservative reserve for the rest of the node. No deeper analysis; it is configurable. Operators with a large--db-cache-sizeor many VMs can raise it.Testing
Verified by running the node and reading the startup log
(
Sierra compilation limits).availableMemory--max-compilation-memory 4096--node-memory-reserve 100000--memory=8g--memory=6gThe Docker rows confirm the value follows the cgroup limit, not the 16 GiB host.
When the budget fits no compilation the limit floors to 1, so the node boots and compiles one at a time instead of refusing to start.