RFC: hugepage/memory fit admission for best-of-K placement (minimal viable change)
Los mantenedores suelen responder en 3 días
Nadie ha tomado este issue todavía.
Evaluación
- Dificultad
- 5/5
- Tiempo estimado
- Más de una semana
- Aptitud para principiantes
- 38/100
- Tipo de issue
- Nueva funcionalidad
- Claridad
- Bastante claro
- Estado de actividad
- Activo
- Stack tecnológico
- go
- Área
- backend, infrastructure
Línea de trabajo
Start in packages/api/internal/orchestrator/placement/placement_best_of_K.go, reading sample(), chooseNode(), the existing memoryScore accounting, and BestOfKConfig. Review the proposed placement unit cases for capacity filtering, no-pool nodes, all-full sets, and the K-sampling fallback; done means the agreed admission behavior, configuration and error handling are covered by tests, with the open rollout and widening questions resolved.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
RFC: Hugepage/memory fit admission for best-of-K placement (minimal viable change)
Summary
Add a hard capacity (hugepage/memory) admission filter to the best-of-K placement sample() so that a node whose hugepage pool cannot hold the requested sandbox is never selected. Today hugepage headroom only influences ranking (memoryScore), never candidacy, so a full node can still win a sample and the request fails downstream on the orchestrator as 500: Failed to place sandbox. This RFC proposes the smallest change that closes that gap, gated behind a config flag with a shadow-then-enforce rollout.
This is the policy counterpart to #3553 (which asks to expose why placement failed) and the direct fix for the problem described in #3656.
Motivation
packages/api/internal/orchestrator/placement/placement_best_of_K.go --- sample() filters candidate nodes on exactly five predicates:
- excluded set
CanAcceptNewRequests()NodeSatisfiesCPUNodeSatisfiesFeatures- label compatibility
None checks whether the node can physically hold the sandbox. Hugepage-pool load appears only as a soft term in memoryScore, used to rank survivors --- it never removes a node from candidacy. So under memory/hugepage pressure the placement engine can (a) pick a full node that then fails on the node side, or (b) when best-of-K samples only full nodes, fail the whole request. Observed on the dev cluster as batch runs where an entire concurrency level returned SandboxException: 500: Failed to place sandbox.
The failure is a placement-admission gap, not (always) genuine cluster exhaustion: the API had enough information locally to reject fast, but instead spent an API---orchestrator---placement round trip to fail slowly.
Goals
- Never forward a create to a node whose hugepage pool cannot hold it.
- Reuse the exact accounting
memoryScorealready uses, so ranking and admission never disagree. - Zero-risk rollout: ship behind a flag, observe in shadow mode, then enforce.
- Preserve today's behavior for nodes that report no hugepage pool.
Non-Goals
- Not raising per-node density. Higher density needs memory sharing / reclamation (KSM, ballooning, proactive reclaim) --- out of scope here.
- Not changing CPU over-commit policy (
R), which is a soft-score dimension and rarely a hard-failure source. - Not the diagnostics surface --- that is #3553; this RFC only references it for the error-differentiation touchpoint.
Proposed design (minimal viable change)
All changes are confined to the placement package; no proto, no orchestrator, no node-agent changes. Node metrics already carry HugePagesTotal / HugePagesUsed / HugePagesReserved / HugePageSizeBytes.
1. Capacity helper (same accounting as memoryScore)
// nodeHasCapacity reports whether the node's hugepage pool can hold this
// sandbox: pool total minus (used + reserved + in-flight pending) must leave
// room for the request. A node reporting no pool (HugePagesTotal == 0) is
// treated as "unknown" and left to the soft score, preserving current behavior.
func nodeHasCapacity(n *nodemanager.Node, resources nodemanager.SandboxResources) bool {
m := n.Metrics()
if m.HugePagesTotal == 0 || m.HugePageSizeBytes == 0 {
return true
}
var pendingMiB int64
for _, r := range n.PlacementMetrics.InProgress() {
pendingMiB += r.MiBMemory
}
need := hugePagesFor(resources.MiBMemory, m.HugePageSizeBytes) +
hugePagesFor(pendingMiB, m.HugePageSizeBytes)
committed := m.HugePagesUsed + m.HugePagesReserved
if committed >= m.HugePagesTotal {
return false
}
return m.HugePagesTotal-committed >= need
}
2. Wire it into sample()
sample() gains the resources nodemanager.SandboxResources parameter (already available in chooseNode, one-line call-site change), and after the label filter:
if config.HugepageFitFilter && !nodeHasCapacity(n, resources) {
nodeFitRejectedCounter.Add(ctx, 1) // observability
continue
}
3. Config flag (mirrors BEST_OF_K_HUGEPAGE_MEMORY)
type BestOfKConfig struct {
R float64
Alpha float64
K int
ScoreHugepages bool
HugepageFitFilter bool // new; env BEST_OF_K_HUGEPAGE_FIT_FILTER, default false
}
4. K-sampling blind spot (the one non-trivial correctness point)
best-of-K samples only K nodes. With enforcement on, all K sampled nodes could be fit-rejected while free nodes exist but simply were not sampled. Mitigation: when the K-sample yields zero candidates and fit-filter is enabled, widen the search (one extra sampling round, or fall back to a full scan) before declaring failure. Without this, enforcement could fail placements that should succeed.
5. Error differentiation (ties into #3553)
When candidates end up empty, distinguish "no node with capacity" from "no compatible node (CPU/feature/label)". This lets operators tell scale up from misconfiguration --- exactly the ambiguity behind opaque Failed to place sandbox today.
Rollout plan
- Shadow (
HugepageFitFilter=false): helper runs,nodeFitRejectedCounterincrements for would-be rejections, but no node is actually filtered. Measure how often a "would-be-rejected" node was in fact the one that later failed placement --- quantifies benefit with zero behavior change. - Enforce (
HugepageFitFilter=true): flip per environment once the metric validates.
Alternatives considered
- Rank-only (status quo): soft
memoryScorealready exists but does not prevent selecting a full node --- this is the bug. - Reject only at orchestrator: already happens; it is the slow round-trip failure we want to avoid.
- Global bin-packing / scheduler rewrite: much larger blast radius; unnecessary to close this specific gap.
Testing
- Unit: near-full node excluded from candidates; all-full node set --- empty candidates + distinct "no capacity" error; no-pool node unaffected; K-blind-spot fallback finds a free node outside the initial sample.
- A/B on a step-laddered replay (fit-filter off vs on): compare
Failed to placerate and placement latency. - Shadow-mode counter validated before enforcing.
Backwards compatibility
Default false --- behavior identical to today until explicitly enabled. Nodes without a reported pool are never hard-blocked. No API/proto/orchestrator changes.
Open questions
- Preferred widening strategy for the K blind spot: extra sampling round vs. full scan on empty result?
- Should the flag be global or per-cluster/per-pool?
- Should CPU over-commit (
R) get a symmetric hard admission later, or stay soft?
Filed per CONTRIBUTING (issue-first for non-trivial changes). Happy to send the PR once the direction and the two open questions above are confirmed. Refs #3656 (problem), #3553 (diagnostics).
- Lenguaje dominante
- Go
- Estrellas
- 1.7k
- Forks
- 468
- Métricas de merge de PR
- Sin PR fusionados en 30 d
Preparar el entorno
Inicia el contenedor de desarrollo del proyecto en tu navegador, con tu propia cuenta de GitHub.
- Sin Dockerfile ni archivo de Docker Compose
- Sin plantilla de pull request
- Leer la guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de e2b-dev/runtime
-
[Bug]: flock() on a mounted volume hangs forever (mount is missing `nolock`)Posiblemente ocupada @AdaAibaby la tomó hace 34 días. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 88/100
Los mantenedores suelen responder en 3 días
-
sandbox cache: StartRemoving state transition not broadcast, all allocations see stale Running statePosiblemente ocupada @AdaAibaby la tomó hace 50 días. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 86/100
Los mantenedores suelen responder en 3 días
-
kernel: ipv6.disable=0 with no IPv6 routing causes Happy Eyeballs latency on all outbound sandbox connectionsPosiblemente ocupada @AdaAibaby la tomó hace 54 días. Abierto
Dificultad 1/5 Menos de una hora Aptitud para principiantes 86/100
Los mantenedores suelen responder en 3 días
-
fix(envd): malformed error message %!w(<nil>) when watch path is not a directoryPosiblemente ocupada @chill-czar la tomó hace 55 días. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 86/100
Los mantenedores suelen responder en 3 días
-
fix(api): POST /sandboxes/{id}/connect accepts non-positive timeout values causing immediate terminationPosiblemente ocupada @chill-czar la tomó hace 55 días. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 88/100
Los mantenedores suelen responder en 3 días
Todos los issues de e2b-dev/runtime
Issues similares
-
raised-by:reviewer
Dificultad 1/5 1-3 horas Aptitud para principiantes 84/100
medici-finance/assay#2681 ·
Los mantenedores suelen responder en 1 día
-
feat(icons): add [solidping]Abierto
Dificultad 1/5 Menos de una hora Aptitud para principiantes 82/100
homarr-labs/dashboard-icons#2939 ·
Los mantenedores suelen responder en 1 día
-
refine bluetooth scan listAbierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 66/100
AvengeMedia/DankMaterialShell#3761 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 77/100
Los mantenedores suelen responder en 1 día
-
docs: clarify replacement for outdated Terms of Service URL in readme.mdPosiblemente ocupada @mohitjoshi-hey la tomó hoy. Abierto
Dificultad 1/5 1-3 horas Aptitud para principiantes 66/100