Skip to content

feat(orchestrator): observe per-template duplicate base-image footprint (density go/no-go, off by default) - #3771

Open
AdaAibaby wants to merge 2 commits into
e2b-dev:mainfrom
AdaAibaby:feat/template-dup-observability
Open

AdaAibaby wants to merge 2 commits into
e2b-dev:mainfrom
AdaAibaby:feat/template-dup-observability

Conversation

@AdaAibaby

Copy link
Copy Markdown
Contributor

Stacked on #3766 (per-sandbox working-set observability), reusing its sandbox-density-metrics flag. Until #3766 merges, the diff shown includes its commit — review the observe per-template duplicate base-image footprint commit.

The go/no-go data floor for same-template page sharing (the biggest remaining density lever in #3763). Observability only, off by default.

Why

Sandboxes of one template (one buildID) already share a single source memfile on the host: the pinned template cache (GetTemplatePinned(buildID)) hands every one the same ReadonlyDevice. But the UFFD handler serves each sandbox's guest RAM via UFFDIO_COPY (MISSING mode), so each faults its own private physical copy of those identical, read-only base pages into the pool. N same-template sandboxes = N physical copies of a source that is already one. Nothing measures what that duplication costs, so there's no basis to justify — or decline — the cross-repo work (FC-fork shared_memfd_path + runtime MINOR/UFFDIO_CONTINUE) that collapsing N→1 would take.

What

Behind the existing sandbox-density-metrics flag (default off):

  • ServeSnapshot.SourceBytes = SourcePages × pageSize — base-image content faulted from the shared source (private written pages excluded; they can't be shared). Derived from an existing counter, no new fault-path work.
  • Sandbox.MemorySourceBytes() exposes it.
  • orchestrator.sandbox.mem.shareable_base_estimate — per-build_id gauge. Resident sandboxes are grouped by build in an in-memory pre-pass (no RPC); each build with ≥2 sandboxes reports (N-1) × min(base footprint) — a lower bound on the pool a single shared base-page copy would free on this node. shareableBaseBytes is pure, table-tested (floor at <2, (N-1)×min shape, a zero member dragging the bound to 0).

Safety / scope

  • Observability only — nothing shared/reclaimed; serve path untouched; off by default → zero behaviour and zero added cardinality.
  • Pure-function test + existing SandboxObserver regression pass; go build/go vet clean; uffd userfaultfd suite green.

How it gates the risky work

Same-template page sharing is a cross-repo, SIGBUS-class, hot-path change (FC fork forward-port of shared_memfd_path from the v1.12 fork — it is not in the deployed v1.14-0.2.0 — plus runtime MINOR-fault support, which today is actively rejected). This PR is the measurement that says whether a node's templates are dense enough for N→1 to free meaningful pool before any of that is attempted. Measure first, then decide.

Refs #3763.

…aim estimate

Density observability, off by default. Guest RAM is faulted into the hugepage
pool lazily (UFFD + hugetlbfs), so a node's real ceiling is the working set
actually faulted in, not the RAM sandboxes nominally declare — and an idle
sandbox keeps holding pool pages it no longer uses, because nothing reclaims
them at runtime. Today there is no per-sandbox visibility into either, so any
reclaim/sharing/tiering work has no data to justify or tune against.

This adds two per-sandbox gauges, gated behind the sandbox-density-metrics
flag (default false → no new series until enabled):

- orchestrator.sandbox.mem.working_set: host bytes faulted into the pool for
  the sandbox since resume, read from the UFFD handler's existing ServeStats
  (no new instrumentation on the fault path) via a new Sandbox accessor
  MemoryWorkingSetBytes. An upper bound on current pool residency.
- orchestrator.sandbox.mem.reclaimable_estimate: pool memory the node holds
  that the guest is not actively using — max(0, workingSet - guestInUse),
  where guestInUse is the envd-reported used RAM. Combined with free-page
  reporting: when the sandbox's balloon returns freed pages itself
  (BalloonModeReporting), the pool is self-reclaiming and the estimate is 0,
  so it does not double-count pages the device already discards.

The estimate feeds a later reclaim decision, so it is floored at zero and
never over-reports; reclaimableBytes is a pure function with table tests
(gap, floor-at-zero, no-guest-report upper bound, free-page-reporting zero).

Scope: observability only — nothing is reclaimed, paused or shared here. It is
the data floor for later density work (live balloon reclaim, same-template
page sharing, pool-pressure auto-pause); see e2b-dev#3763. Off by default means zero
behaviour and zero added metric cardinality until an operator turns it on.

Refs e2b-dev#3763.
The data floor for same-template page sharing. Sandboxes of one template
(one buildID) already share a single source memfile on the host — the pinned
template cache (GetTemplatePinned(buildID)) hands every one the same
ReadonlyDevice, so the base image exists once as a source. But the UFFD
handler serves each sandbox's guest RAM via UFFDIO_COPY (MISSING mode), so
every sandbox faults its OWN private physical copy of those identical,
read-only base pages into the pool. N same-template sandboxes = N physical
copies of a source that is already one. Nothing today measures how much pool
that duplication costs, so there is no basis to justify (or decline) the
cross-repo page-sharing work it would take to collapse N to 1.

This adds, behind the existing sandbox-density-metrics flag (default off):

- ServeSnapshot.SourceBytes = SourcePages x pageSize: the base-image content a
  sandbox faulted from the shared source (private written pages excluded — they
  cannot be shared). Derived from an existing counter, no new fault-path work.
- Sandbox.MemorySourceBytes() exposes it.
- orchestrator.sandbox.mem.shareable_base_estimate, a per-build_id gauge: the
  resident sandboxes are grouped by build in an in-memory pre-pass (no RPC) and
  each build with >= 2 sandboxes reports (N-1) x the smallest base footprint
  among them — a lower bound on the pool a shared base-page copy would free on
  this node. shareableBaseBytes is a pure function with table tests (floor at
  <2, (N-1)*min shape, a zero member dragging the bound to 0).

Scope: observability only. Nothing is shared, reclaimed, or changed in the
serve path; off by default means zero behaviour and zero added cardinality
until enabled. It is the go/no-go evidence for same-template page sharing
(the FC-side shared_memfd_path mechanism exists as prior art in the fork but
is not in the deployed FC, and the runtime has no MINOR-fault support yet):
turn it on, and if a node's templates are dense enough for N->1 to free
meaningful pool, the gauge says so before any risky cross-repo work.

Builds on the per-sandbox working-set observability (same flag). Refs e2b-dev#3763.

This branch has not been deployed

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants