Fix/worker oom kill - #47
Merged
Merged
Conversation
The Celery worker was being SIGKILL'd (signal 9) during pipeline execution because the ONNX segmentation model + image processing exceeded the container's memory limit. Since SIGKILL cannot be caught, the job's exception handler never ran, leaving it stuck in 'processing' forever. Changes: - docker-compose.yml: Add mem_limit (4g) and memswap_limit (6g) to the worker service, with env overrides (WORKER_MEM_LIMIT/WORKER_MEMSWAP_LIMIT) - celery_app.py: Add task_acks_late + task_reject_on_worker_lost so tasks are re-queued when a worker dies; add task_time_limit (600s) and task_soft_time_limit (540s); add worker_max_memory_per_child (3 GiB) to recycle worker processes before OOM - segmenter.py: Process ONNX inference one page at a time instead of batching all pages, reducing peak memory from N*page to 1*page - cleanup.py: Add cleanup_stale_jobs Beat task (every 5 min) that marks jobs stuck in 'processing' longer than JOB_STALE The Celery worker was being SIGKILL'd (signal 9) during pipeline executs -because the ONNX segmentation model + image processing exceeded the contaxacontainer's memory limit. Since SIGKILL cannot be caught, the job'ERexception handler never ran, leaving it sment new env vars, stale-jo Changes: - docker-compose.yml: Add mem_limit (4g) and memswap_limit Upd- dockeke worker service, with env overrides (WORKER_MEM_LIMIT/WORKER_MEMSWAPal- celery_app.py: Add task_a
- Add typings/onnxruntime/__init__.pyi stub for InferenceSession, get_inputs, and run methods to satisfy strict Pylance/pyright type checking - Add ellipsis body to SemanticSegmenter Protocol method - Cast HoughLinesP result to np.ndarray | None since cv2 stubs claim it never returns None but it can at runtime - Cast _require_image_array return value to fully-typed np.ndarray - Add pyright: ignore[reportPrivateUsage] comments to test file for accessing the private _cleanup_stale_jobs_async helper
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.
Fix: Celery Worker OOM Kill and Stale Job Recovery
Root Cause
The Celery worker was being killed by SIGKILL (signal 9) during pipeline execution because the ONNX segmentation model + image processing exceeded the container's memory limit. Since SIGKILL cannot be caught, the job's exception handler never ran, leaving jobs stuck in
processingforever.Changes (2 commits on
fix/worker-oom-kill)Commit 1 (
db87429): OOM prevention and stale job recoverydocker-compose.yml— Addedmem_limit: 4gandmemswap_limit: 6gto the worker service with env overrides (WORKER_MEM_LIMIT/WORKER_MEMSWAP_LIMIT). AddedJOB_STALE_TIMEOUT_SECONDSto worker and beat environments.backend/app/tasks/celery_app.py— Added Celery safety settings:task_acks_late=True+task_reject_on_worker_lost=True— re-queue tasks when a worker diestask_time_limit=600/task_soft_time_limit=540— prevent hung tasksworker_max_memory_per_child=3 GiB— recycle worker processes before OOMcleanup-stale-jobsbeat schedule (every 5 minutes)backend/app/pipeline/steps/segmenter.py— RefactoredOnnxSemanticSegmenter.segment()to process pages one at a time instead of batching all pages into a singlenp.stack. Peak memory reduced fromN_pages × page_tensor_sizeto1 × page_tensor_size.backend/app/tasks/cleanup.py— Addedcleanup_stale_jobstask that finds jobs stuck inprocessingfor longer thanJOB_STALE_TIMEOUT_SECONDSand marks them asfailed.backend/app/core/config.py— AddedJOB_STALE_TIMEOUT_SECONDSsetting (default 300s)..env.example— Documented new env vars.docs/operations.md&docs/risks-and-edge-cases.md— Documented new settings, cleanup tasks, and OOM troubleshooting.backend/tests/test_stale_job_recovery.py— 3 new tests for stale job recovery.Commit 2 (
63ef6c1): Pylance type fixestypings/onnxruntime/__init__.pyi— New type stub forInferenceSession,get_inputs,runmethods to satisfy strict Pylance/pyright type checking.backend/app/pipeline/steps/segmenter.py:SemanticSegmenterProtocol methodHoughLinesPresult tonp.ndarray | Nonesince cv2 stubs claim it never returns None but it can at runtime_require_image_arrayreturn value to fully-typednp.ndarraybackend/tests/test_stale_job_recovery.py— Added# pyright: ignore[reportPrivateUsage]comments for accessing the private_cleanup_stale_jobs_asynchelper.Defense in Depth
worker_max_memory_per_child(3 GiB) + per-page segmenter processingtask_acks_latere-queues the task on a fresh workerprocessingjobs asfailedafter 5 minutes