Skip to content

fix: read cgroup memory limit for cache sizing when GOMEMLIMIT is not set - #7573

Open
ycombinator wants to merge 7 commits into
elastic:mainfrom
ycombinator:fix/cgroup-aware-memory-limit
Open

fix: read cgroup memory limit for cache sizing when GOMEMLIMIT is not set#7573
ycombinator wants to merge 7 commits into
elastic:mainfrom
ycombinator:fix/cgroup-aware-memory-limit

Conversation

@ycombinator

@ycombinator ycombinator commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

What is the problem this PR solves?

When GOMEMLIMIT is not explicitly set, containerMemoryMB() (introduced in #7568) falls back directly to host RAM. A container with a cgroup memory limit but no explicit GOMEMLIMIT would use the node's full RAM for memory-based sizing decisions in fleet-server.

How does this PR solve the problem?

Extends containerMemoryMB() with a cgroup tier between GOMEMLIMIT and host RAM:

  1. GOMEMLIMIT — used when explicitly set
  2. cgroup v2 — reads /sys/fs/cgroup/memory.max; skips if value is "max" (unlimited)
  3. cgroup v1 — reads /sys/fs/cgroup/memory/memory.limit_in_bytes; skips if value ≥ MaxInt64 (unlimited sentinel)
  4. host total RAM — final fallback for non-containerised deployments

On non-Linux hosts the cgroup files simply don't exist, so the reads fail silently and the fallback chain continues — no platform-specific build tags needed.

How to test this PR locally

go test ./internal/pkg/config/... -run "TestContainerMemoryMB|TestReadCgroupMemoryFile" -v

TestReadCgroupMemoryFile covers: valid byte-limit, "max" unlimited sentinel, MaxInt64 unlimited sentinel (cgroup v1), missing file, and invalid content.

Design Checklist

  • I have ensured my design is stateless and will work when multiple fleet-server instances are behind a load balancer.
  • I have or intend to scale test my changes, ensuring it will work reliably with 100K+ agents connected.
  • I have included fail safe mechanisms to limit the load on fleet-server: rate limiting, circuit breakers, caching, load shedding, etc.

Checklist

  • I have added tests that prove my fix is effective or that my feature works
  • I have added an entry in ./changelog/fragments using the changelog tool

Related issues

@mergify

mergify Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

This pull request does not have a backport label. Could you fix it @ycombinator? 🙏
To fixup this pull request, you need to add the backport labels for the needed
branches, such as:

  • backport-./d./d is the label to automatically backport to the 8./d branch. /d is the digit
  • backport-active-all is the label that automatically backports to all active branches.
  • backport-active-8 is the label that automatically backports to all active minor branches for the 8 major.
  • backport-active-9 is the label that automatically backports to all active minor branches for the 9 major.

@ycombinator
ycombinator requested review from lorienhu and removed request for blakerouse August 6, 2026 23:21
@ycombinator ycombinator added Team:Elastic-Agent-Control-Plane Label for the Agent Control Plane team backport-active-all Automated backport with mergify to all the active branches labels Aug 6, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves memory-based sizing decisions in Fleet Server’s config defaults by making containerMemoryMB() cgroup-aware when GOMEMLIMIT is not explicitly set, so cache sizing better reflects the effective container limit rather than host RAM.

Changes:

  • Extend containerMemoryMB() to prefer: GOMEMLIMIT → cgroup v2 → cgroup v1 → host RAM.
  • Add unit tests for cgroup limit file parsing and for containerMemoryMB() selection behavior.
  • Add changelog fragments documenting the behavior change.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.

File Description
internal/pkg/config/env_defaults.go Adds cgroup-based memory limit detection and updates logging around detected memory.
internal/pkg/config/env_defaults_test.go Adds tests for containerMemoryMB() and cgroup file parsing behavior.
changelog/fragments/1786058255-fix-cgroup-aware-cache-memory.yaml Documents the new cgroup fallback tier when GOMEMLIMIT is unset.
changelog/fragments/1786038287-fix-cache-container-memory.yaml Adds a changelog entry whose description should be updated to match the new cgroup-aware behavior.
Suppressed comments (1)

internal/pkg/config/env_defaults.go:375

  • To make containerMemoryMB() tests hermetic, expose the cgroup lookup via a package-level var (similar to memMB) so tests can temporarily override it.
// memMB returns available memory in MiB.
// It is a var so that unit tests can replace it.
var memMB func() uint64 = containerMemoryMB

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread internal/pkg/config/env_defaults.go
Comment thread changelog/fragments/1786038287-fix-cache-container-memory.yaml Outdated
Comment thread internal/pkg/config/env_defaults.go Outdated
Comment thread internal/pkg/config/env_defaults_test.go Outdated
Copilot AI review requested due to automatic review settings August 6, 2026 23:24
@ycombinator
ycombinator marked this pull request as draft August 6, 2026 23:24

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.

Suppressed comments (1)

internal/pkg/config/env_defaults_test.go:111

  • This test assumes that clearing GOMEMLIMIT always forces a host-RAM fallback, but containerMemoryMB() now checks cgroup limits before host RAM. On Linux CI/container environments where a cgroup limit is set, this assertion can fail even though the production behavior is correct. Make the expected value conditional on whether a cgroup limit is detected so the test is environment-independent.
		prev := debug.SetMemoryLimit(math.MaxInt64)
		t.Cleanup(func() { debug.SetMemoryLimit(prev) })

		got := containerMemoryMB()
		assert.Equal(t, memory.TotalMemory()/1024/1024, got)

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

TL;DR

The failing Buildkite unit-test step is inconclusive from the available log snippet: go test -race exited with code 1, but the captured log contains no explicit test failure/panic/data-race line. This currently looks like a transient/flaky CI failure rather than a deterministic regression in this PR.

Remediation

  • Re-run the failed Buildkite job once to confirm whether this reproduces.
  • If it fails again, capture and attach the full go test -tags=grpcnotrace -v -race ./... output (including earlier lines) so the first failing package or race report is visible.
  • If the rerun is clean, proceed (no code change needed from this signal alone).
Investigation details

Root Cause

The log available at /tmp/gh-aw/buildkite-logs/fleet-server-smartbear-testexecute-run-unit-tests.txt ends with:

  • all shown tests passing (e.g. internal/pkg/throttle, internal/pkg/ver)
  • then a bare FAIL and go test ... exit code 1

Because no failing test name, panic, or WARNING: DATA RACE line is present in the captured snippet, a specific code-level root cause cannot be proven from this artifact alone.

Evidence

  • Build: https://buildkite.com/elastic/fleet-server/builds/16138
  • Job/step: Run unit tests (.buildkite/scripts/unit_test.sh)
  • Key log excerpt:
    • FAIL
    • Error: running "go test -tags=grpcnotrace -v -race -coverprofile=build/coverage-linux.out ./..." failed with exit code 1
    • No accompanying test failure/race block in the provided snippet

Verification

  • Re-ran go test -tags=grpcnotrace -v -race -coverprofile=build/coverage-linux.out ./... on commit 79ae4217b48ece4f936e054c0d2f8ad33ed1471a in this environment: pass.
  • Re-ran targeted new tests from this PR (TestContainerMemoryMB, TestReadCgroupMemoryFile): pass.

Follow-up

  • I checked open flaky-test issues in this repo; none clearly match this specific unit-test failure signature.

What is this? | From workflow: PR Buildkite Detective

Give us feedback! React with 🚀 if perfect, 👍 if helpful, 👎 if not.

ycombinator and others added 3 commits August 6, 2026 19:00
… set

Extend containerMemoryMB() to try the cgroup memory limit (v2 at
/sys/fs/cgroup/memory.max, then v1 at
/sys/fs/cgroup/memory/memory.limit_in_bytes) before falling back to host
RAM. This means the ristretto cache is correctly sized even in deployments
that do not explicitly set GOMEMLIMIT.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
containerMemoryMB is used for general memory-based sizing in fleet-server,
not solely for the ristretto cache tier.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This fragment belongs to PR elastic#7568. Removing it here so it doesn't appear
twice in the diff against main. It will re-enter via main once elastic#7568 merges.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@mergify

mergify Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

This pull request is now in conflicts. Could you fix it @ycombinator? 🙏
To fixup this pull request, you can check out it locally. See documentation: https://help.github.com/articles/checking-out-pull-requests-locally/

git fetch upstream
git checkout -b fix/cgroup-aware-memory-limit upstream/fix/cgroup-aware-memory-limit
git merge upstream/main
git push upstream fix/cgroup-aware-memory-limit

Copilot AI review requested due to automatic review settings August 7, 2026 13:08
@ycombinator
ycombinator force-pushed the fix/cgroup-aware-memory-limit branch from 79ae421 to c21d6ad Compare August 7, 2026 13:08

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.

Suppressed comments (2)

internal/pkg/config/env_defaults_test.go:112

  • This subtest still asserts a direct fallback to host RAM when GOMEMLIMIT is unset, but containerMemoryMB() now checks cgroup limits before host RAM. That makes this assertion incorrect and potentially environment-dependent (it will fail when tests run in a container with a finite cgroup limit). Update the expectation to follow the same priority order as the production code.
		assert.Equal(t, memory.TotalMemory()/1024/1024, got)
	})

changelog/fragments/1786058255-fix-cgroup-aware-cache-memory.yaml:1

  • This change is described and implemented as a fix for incorrect cache sizing that can lead to OOMKills; other similar fixes in this repo use kind: bug-fix. Consider using bug-fix here so release notes categorize it correctly.
kind: enhancement

@ycombinator
ycombinator marked this pull request as ready for review August 7, 2026 14:07
@ycombinator
ycombinator enabled auto-merge (squash) August 7, 2026 14:07
Copilot AI review requested due to automatic review settings August 7, 2026 14:30

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.

return 0, false
}
n, err := strconv.ParseUint(s, 10, 64)
if err != nil || n >= math.MaxInt64 {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to this the sentinel value for cgroup v1 is not quite MaxInt64 because it's rounded. Double checked here.

// cgroupMemoryLimitMB reads the container memory limit from cgroup files.
// It tries cgroup v2 first, then cgroup v1. Returns (0, false) when no
// applicable limit is found (unlimited, missing file, or parse error).
func cgroupMemoryLimitMB() (uint64, bool) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might want to add a comment here that this doesn't work with nested cgroups.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport-active-all Automated backport with mergify to all the active branches Team:Elastic-Agent-Control-Plane Label for the Agent Control Plane team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants