Skip to content

fix(fly): size zygote pool + slots to real RAM to stop concurrency OOM#17

Open
teovillanueva wants to merge 1 commit into
mainfrom
fix/zygote-pool-concurrency-sizing
Open

fix(fly): size zygote pool + slots to real RAM to stop concurrency OOM#17
teovillanueva wants to merge 1 commit into
mainfrom
fix/zygote-pool-concurrency-sizing

Conversation

@teovillanueva

Copy link
Copy Markdown
Owner

Problema

edalef reportó ejecuciones que "se clavan" bajo concurrencia (exámenes). Root cause: el tier zygote mete todos los jobs de Python concurrentes en un solo contenedor-pool compartido por (lenguaje,versión), pero su cap de memoria (ZYGOTE_POOL_MEMORY_MB=1024) solo entraba ~2 jobs con el memoryMb=512 que usa edalef para data-science — mientras el worker seguía anunciando 24 slots planos.

Bajo carga, el cgroup del pool de 1 GB hacía OOM. A veces el OOM-killer mataba al parent privilegiado del pool (observado en prod: python UID:0 killed → zygote: read STARTED: EOF → fallback a Docker tier), tumbando todos los jobs in-flight que compartían ese pool.

Cambio (Camino A — solo config, sin resize de VM)

Ya desplegado y verificado en prod:

Parámetro Antes Después
ZYGOTE_POOL_MEMORY_MB (worker) 1024 2560
WORKER_MAX_SANDBOXES (worker) 24 5
FAS_STARTED_MACHINE_COUNT divisor (autoscaler) /24 /5
  • El pool usa la RAM ociosa de la VM de 4 GB (5 × 512 MB con ~1.3 GB para dockerd/OS).
  • El slot se adquiere antes de forkear el hijo del pool → un contador = usableRAM/512MB lo convierte en gate de admisión real: el worker rechaza el 6º job en vez de sobrecargar el pool.
  • El divisor del autoscaler matchea la capacidad real por nodo (un solo nodo OOM-eaba mucho antes de 24 inflight).

Verificación

  • Smoke job Python → state: "done"
  • Pool container en vivo → mem.limit = 2684354560 = 2560 MiB

Follow-ups (fuera de este PR)

  • Camino B: memory-weighted admission en el worker (desacopla el contador de slots de la RAM real; maneja jobs de distinto tamaño). Es el fix durable.
  • Capacidad de pico: solo hay 3 máquinas pre-creadas (~15 Python concurrentes). Pre-crear más con provision-pool.sh grow N + subir el ceiling del autoscaler para exámenes más grandes.

🤖 Generated with Claude Code

The zygote tier funnels every concurrent Python job into ONE shared pool
container, but its memory cap (ZYGOTE_POOL_MEMORY_MB=1024) only fit ~2 jobs
at edalef's memoryMb=512, while the worker still advertised 24 flat slots.
Under exam-time concurrency the 1 GB pool cgroup OOM'd — sometimes killing the
privileged pool *parent* (observed: python UID:0 killed → "zygote: read STARTED:
EOF" → Docker-tier fallback), collapsing every in-flight job sharing that pool.

Camino A (config-only, no VM resize), deployed + verified in prod:
- ZYGOTE_POOL_MEMORY_MB 1024 -> 2560 (uses the idle RAM on the 4 GB VM; holds
  5 x 512 MB jobs with ~1.3 GB left for dockerd/OS).
- WORKER_MAX_SANDBOXES 24 -> 5: the slot semaphore is acquired before the pool
  child is forked, so a count matching usableRAM/512MB makes it a real admission
  gate — the worker refuses the 6th job instead of overcommitting the pool.
- Autoscaler divisor /24 -> /5 so the fleet scales out at real per-node capacity
  (a single node's pool OOM'd long before 24 inflight).

Verified: python smoke job state=done; live pool mem.limit=2684354560 (2560 MiB).

Follow-ups (not in this change): memory-weighted admission in the worker
(decouple slot count from RAM) is the durable fix; pre-create more pool machines
+ raise the autoscaler ceiling for larger exams.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 18, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
code-runner-docs Ready Ready Preview, Comment Jun 18, 2026 4:01pm

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant