chore(runway): correct what GOMEMLIMIT accounts for

Summary

The GOMEMLIMIT comment in .runway/values.yaml told an operator to budget the gap below limits.memory for goroutine stacks and runtime metadata. The ceiling already accounts for both, so the gap read larger than it is.

runtime/debug.SetMemoryLimit documents the limit as "all memory mapped, managed, and not released by the Go runtime" and gives the quantity the runtime maintains as MemStats.Sys - MemStats.HeapReleased. Sys covers the heap, the stacks and the other runtime data structures, so StackSys and GCSys sit inside the 400 MiB ceiling rather than in the 112 MiB gap beneath the 512Mi limits.memory.

What changed

.runway/values.yaml now says what the ceiling covers, then what the gap below it is for, which is two costs rather than one.

  • What the ceiling excludes: the binary's own image and the OS kernel memory held on behalf of the process. The doc's other exclusions are inert here, so listing them would send an operator hunting a term that is always zero. The image the workload pulls is built with CGO_ENABLED=0 (the builds env block in .goreleaser.yml, which docs/dev/releases.md names under "The image is built on the tag pipeline" as the build that produces it). So there are no C allocations, and nothing in the build calls syscall.Mmap: no hit under internal/ or cmd/, and none in the dependency sources go list -deps ./cmd/artifact-registry resolves either, where the only matches are test files in golang.org/x/sys.
  • The ceiling's own overshoot: GOMEMLIMIT is soft. The collector cannot drive the heap goal below the live set, and the GC CPU limiter caps GC CPU at 50% (runtime/mgclimit.go) rather than collecting without bound, so past that point the footprint grows through the ceiling and the cgroup limit is what stops it. Naming only the exclusions would leave the gap reading smaller than it is, which is the same defect the other way round.

The replacement does not claim either figure is an RSS measurement, because Sys is reserved address space rather than resident memory.

.runway/fairway.yaml changes in three places.

  • It carried the same framing by reference, as "the non-heap RSS margin .runway/values.yaml describes", so it moves with the wording it points at.
  • It had two sentences saying these values must not move while the OOM work item stayed open. That work item closed on 2026-09-03, with the fix confirmed on staging, so as written the block now reads as a freeze with no end. It points at the work item that owns the sizing instead.
  • Its sizing recipe said "under a push load" with no scale qualifier. maxInstances lets the HPA scale out, so a load split across replicas reads as a floor rather than a peak, and the one staging measurement that exists was taken that way. The recipe now says the load has to be sustained and taken by one replica.

What this does not do

The numbers are unchanged: requests.memory: 256Mi, limits.memory: 512Mi, GOMEMLIMIT: "400MiB". Sizing them needs a sustained single-pod push load and a Mimir query, which is the other half of the linked work item and does not block this correction, so this MR does not close it.

Testing

Comments only, and outside the comment-caps gate: scripts/ci/check-comment-caps.sh diffs with -- '*.go', so a YAML block is not in its scope. Both files re-parse under yaml.safe_load, and GOMEMLIMIT reads back as 400MiB.

No other open merge request touches .runway/: re-checked at head d451257f1 against the changed-file list of all 91 merge requests open at that point, one of which is this one. So there is no merge order to state.

Related to #1137 (closed)

Edited by Dzmitry (Dima) Meshcharakou

Merge request reports

Loading
Loading