Bound celery pool-child RSS so one runaway task can't take the container - #5475
Open
blarghmatey wants to merge 1 commit into
Open
Bound celery pool-child RSS so one runaway task can't take the container#5475blarghmatey wants to merge 1 commit into
blarghmatey wants to merge 1 commit into
Conversation
mitlearn-default-celery-worker has been OOMKilling on applications-production continuously since 2026-08-13, at 15-24 container restarts an hour. The celery worker command sets --max-tasks-per-child 100 with no memory-based recycle trigger, so a child that balloons on task 1 stays resident for the next 99 and the kernel kills the whole container -- taking unrelated in-flight tasks with it. Add max_memory_per_child_kib to OLApplicationK8sCeleryWorkerConfig, emitting --max-memory-per-child only when set. Defaulting to None keeps every other OLApplicationK8s consumer's recycling behaviour unchanged; this flag decides when celery retires a pool child, so it should not arrive as a silent default. Set it to 640Mi on mit_learn's default worker. A healthy child on that queue sits at ~165Mi (whole container ~480Mi for master + 2 children, observed 2026-08-17), so it only fires on a child that has genuinely ballooned. Sized against the floor-derived limit -- 1Gi request x the declared 2:1 ratio = 2Gi -- rather than the 2560Mi declared or the 6144Mi the VPA currently enforces, because the floor is the smallest limit a pod can run under. Left off the embeddings and edx_content workers deliberately. The embeddings worker holds an ML model, so recycling on memory would mean reloading it. This bounds carry-over between tasks, not a single task: celery checks RSS after a task returns, so a task that blows the cgroup limit in one execution still OOMs. That is what mitodl/mit-learn#3780 fixes -- the PostHog view-event ETL retaining the whole backlog in memory. This is the guard, not the cure, and it does not clear the alert on its own. Note the VPA on this deployment is already pegged at its ceiling: requests read exactly 3Gi (= _worker_vpa_bounds max_allowed) against a 6144MiB enforced limit vs 2560MiB declared. Once memory is bounded, max_allowed is worth re-tightening -- every replica currently reserves 3Gi against up to 20 replicas while fresh pods sit at 480Mi. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011yGLhunNHrh4DLyujeH7QR
Contributor
There was a problem hiding this comment.
Pull request overview
Adds an opt-in Celery child-memory recycle guard, applied only to MIT Learn’s default worker.
Changes:
- Adds validated
max_memory_per_child_kibconfiguration. - Emits
--max-memory-per-childwhen configured. - Adds coverage for defaults, argument rendering, and validation.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
src/ol_infrastructure/components/services/k8s.py |
Adds and renders the optional Celery memory threshold. |
src/ol_infrastructure/applications/mit_learn/__main__.py |
Configures a 640 MiB threshold for the default worker. |
tests/ol_infrastructure/components/services/test_k8s_extra_containers.py |
Tests threshold behavior and validation. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Ardiea
approved these changes
Aug 18, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What are the relevant tickets?
https://github.com/mitodl/hq/issues/12673 — the application-side fix is mitodl/mit-learn#3780. This is the infrastructure-side guard; the two are independent.
Description (What does it do?)
mitlearn-default-celery-workerhas been OOMKilling onapplications-productioncontinuously since 2026-08-13, at 15–24 container restarts an hour.The celery worker command sets
--max-tasks-per-child 100with no memory-based recycle trigger, so a child that balloons on task 1 stays resident for the next 99 and the kernel kills the whole container — taking unrelated in-flight tasks with it.max_memory_per_child_kibtoOLApplicationK8sCeleryWorkerConfig, emitting--max-memory-per-childonly when set. Defaults toNone, so every otherOLApplicationK8sconsumer is unchanged — this flag decides when celery retires a pool child, and shouldn't arrive as a silent default across every application.655360(640Mi) on mit_learn'sdefaultworker. A healthy child on that queue sits at ~165Mi — the whole container runs ~480Mi for master + 2 children — so it only fires on a child that has genuinely ballooned.--concurrency=2and_worker_vpa_bounds["min_allowed"]— revisit the three together.embeddingsoredx_contentworkers. The embeddings worker holds an ML model, so recycling on memory would mean reloading it.How can this be tested?
pytest tests/ol_infrastructure/components/services/— 114 passed, including three new cases: flag absent by default, flag and value emitted as separate argv entries, and non-positive values rejected by the validator.pre-commit runon the changed files — all hooks pass, including mypy.pulumi preview --stack Productionshows the celery Deployment as an update, not a replacement, touching only the command array:Only the
defaultworker's Deployment appears —edx-contentandembeddingsare untouched, which confirms the opt-in default behaves as intended.Additional Context
This is the guard, not the cure. Celery checks child RSS after a task returns, so it bounds what a child carries into the next task. A single task that blows the cgroup limit within one execution still OOM-kills the container. That is what mitodl/mit-learn#3780 fixes — the PostHog view-event ETL was retaining the entire S3 backlog in memory, on both the extract and load sides. This PR does not clear the alert on its own.
Resizing is not an option here. The VPA on this deployment is already pegged at its ceiling: requests read exactly 3Gi (=
_worker_vpa_bounds["max_allowed"]["memory"]) against a 6144MiB enforced limit vs 2560MiB declared. The 2:1 ratio and 1Gi floor added after earlier incidents guard the floor; here the ceiling binds, so raisingmax_allowedonly moves the wall.Follow-up worth doing once memory is bounded: re-tighten
max_allowedback down from 3Gi. Every replica currently reserves 3Gi against up to 20 replicas while fresh pods sit at 480Mi.Unrelated pending drift, visible in the same
pulumi previewand not from this branch — worth knowing before applying, since it would ride along: thefastly-providerversion, thefastly-mit_learn-productiongzip content-type/extension list ordering, and themitlearn-webapp-pod-monitorselector labels.