← Blog
Three green ceramic capsules fill a cream sorting tray, with a coral capsule waiting beside it.
Published on
2026-11-02

Celery workers: choose concurrency and CPU capacity

A Celery worker receives task messages and runs the corresponding Python functions. Its concurrency setting controls how many tasks its execution pool can work on at once. To choose that setting, start with the work each task does, the memory it needs, and how long a job can wait. A VM's advertised vCPU count alone cannot answer those questions.

This guide is for an application with a working queue. If you still need a producer, broker, and worker example, use the Celery with Redis deployment guide. If you are deciding whether a queue is necessary, start with FastAPI background tasks.

Validation status, November 2, 2026: The documentation and hypothetical calculations were checked, and the command options below were checked against installed Celery 5.6.3 help. No worker, broker, container, Linux VM, or Hivenet workload was run for this article. The sizing experiments, inspection commands, shutdown behavior, and recovery checks remain untested. The example numbers are assumptions, not benchmarks.

Separate a worker instance from its concurrency

A worker instance manages an execution pool. With the prefork pool, concurrency 2 means two child processes can execute tasks, alongside the main worker process. Two such worker instances on the same VM would offer four execution slots while sharing the same CPU and memory budget. Adding instances does not add hardware.

In an environment where the application and broker connection are already configured, this is the command form for a two-process worker:

celery -A tasks:app worker --pool=prefork --concurrency=2 --loglevel=INFO

Here, tasks:app refers to the application in the linked deployment example. The value 2 is illustrative, not a recommendation. For that Docker Compose setup, change the existing worker's startup configuration; do not start an extra worker unintentionally. Keep the broker and credentials private. The Celery workers guide documents worker options and naming multiple instances.

Choose a pool for the task's actual work

Prefork is Celery's default pool and its usual starting point for CPU-bound tasks. Separate processes suit work such as Python calculations, parsing, or document transformation. Threads and greenlet pools have different behavior; eventlet and gevent are intended for compatible I/O-heavy workloads. Changing pools can remove features such as soft timeouts, so check the current concurrency documentation before switching.

A task that spends most of its elapsed time waiting for a remote response may benefit from more concurrent requests, but the remote service's limits still matter. A task that keeps the CPU busy may only create more contention when you increase concurrency. Database connections, file descriptors, and outbound request limits can become constraints before CPU use reaches its ceiling.

Also inspect the libraries inside a task. A numerical library or subprocess may use multiple threads or processes of its own. Four Celery slots do not necessarily mean four CPU threads. Record that internal parallelism when comparing configurations, and keep it fixed during the first experiment.

Measure CPU time, execution time, and queue delay separately

Use representative inputs, including larger valid jobs. Record these measurements for each task type rather than mixing a short notification with a long export:

  • Queue delay: time from accepted submission to the start of execution.
  • Execution time: elapsed time from task start to completion or failure.
  • CPU time: processor time used by the work, including relevant subprocesses.
  • Memory: baseline and peak use for the worker, its children, and the full container or VM.
  • Outcomes: successful jobs, failed attempts, retries, and missing or duplicated results.

For Python instrumentation, time.perf_counter() measures elapsed intervals, while time.process_time() measures CPU time in the current process and excludes sleep. Neither automatically accounts for a separately launched child process. See the Python time reference. Measure queue delay through your job records or events with a consistent clock strategy; do not subtract unrelated monotonic clocks on different machines.

Track both averages and the slower end of the distribution, such as the 95th percentile. An average helps estimate total work, but it can hide the jobs that miss a deadline. Keep failed attempts in the resource accounting, and report successful completion separately so a failure loop cannot look like useful throughput.

Use arithmetic to choose a starting experiment

Suppose a hypothetical workload receives 24 jobs per minute, or 0.4 jobs per second. Each job averages 3 CPU-seconds and 4 seconds of elapsed execution time on the candidate machine. Assume an illustrative target occupancy of 65% to leave room for variability. This percentage is a planning choice, not a Celery default or a guaranteed safe threshold.

  • Average CPU demand: 0.4 × 3 = 1.2 CPU-seconds of work per second.
  • CPU budget with the illustrative margin: 1.2 ÷ 0.65 ≈ 1.85 CPU equivalents on that measured machine.
  • Execution slots to try: round up (0.4 × 4 ÷ 0.65) = 3 concurrent slots.

These estimates answer different questions. CPU demand estimates processor work; the slot calculation includes time a task spends waiting. Neither predicts tail latency, burst handling, scheduling overhead, or throughput once tasks compete for resources. If concurrency changes task duration, calculate again from the new measurements.

Do not translate 1.85 CPU equivalents directly into a promise that any two-vCPU VM will suffice. Processor type, allocation rules, container limits, and other workloads affect the available capacity. Use the vCPU vs. physical core guide to interpret the offer, then test the same task mix on the proposed VM.

Check whether memory rules out that concurrency

For another hypothetical estimate, suppose each active child needs 220 MiB, the main worker needs 90 MiB, and you reserve 768 MiB for the OS, broker, and other services. Three children give 3 × 220 + 90 + 768 = 1,518 MiB before an additional safety margin. These assumed figures are not measurements of the linked sample.

Use this as an early rejection check, then measure the whole workload. Summing process resident memory can count shared pages more than once, while a single snapshot can miss a task's peak. Inspect container or VM totals alongside process measurements, include prefetched messages and results, and repeat large valid inputs. Do not choose a machine whose limit merely equals the estimated total.

Celery's worker_max_memory_per_child can replace a child after it exceeds the configured memory threshold and finishes its task. It is not a hard limit that stops the task before a memory spike. worker_max_tasks_per_child recycles children after a task count. Both need workload-specific settings; see the configuration reference.

Inspect where work is waiting

These unexecuted commands apply to the running, privately configured Compose example. They ask Celery workers for current statistics, active tasks, and reserved tasks:

docker compose exec worker celery -A tasks:app inspect stats
docker compose exec worker celery -A tasks:app inspect active
docker compose exec worker celery -A tasks:app inspect reserved

Reserved tasks have been prefetched but are not yet executing; they are different from tasks still at the broker. Inspection is a snapshot, and a missing reply can mean a timeout or connectivity problem. It is not proof that the queue is empty. Consult the Celery monitoring guide and combine worker observations with broker backlog and your job records. Inspection output can contain task arguments, so keep it private.

If waiting work keeps growing at a stable arrival rate, identify the constraint before adding processes. High CPU use suggests a different experiment from low CPU use with saturated database connections. A healthy worker heartbeat says little about whether jobs finish within their required time.

Separate long and short tasks before tuning prefetch

Long exports can occupy the same execution slots that short user-facing jobs need. Separate queues and worker groups let you assign capacity to each task class. Configure both the producer's routing and the workers that consume those queues. Moving a task to a queue without a consumer only moves the backlog. The routing guide explains task_routes and queue selection.

Prefetch controls how much work a worker reserves ahead of execution. Celery combines worker_prefetch_multiplier with concurrency. With early acknowledgment, a multiplier of 1 can still allow executing tasks plus extra reserved tasks; it does not simply mean one task total per slot. The optimization guide explains that distinction and the tradeoff between long and short jobs.

The Redis deployment example already uses a multiplier of 1 and late acknowledgment for its pure calculation. Keep those settings fixed during an initial concurrency comparison. Do not change acknowledgment behavior merely to reduce memory: repeated execution and failure handling need their own review. Celery 5.6 also offers Redis-only worker_disable_prefetch; it is a separate option to assess, not a change tested here.

Run a controlled concurrency comparison

The following is a proposed test procedure. It was not executed for this article.

  1. Define the requirement. Write down acceptable queue delay, completion time, error rate, and memory headroom for each task class.
  2. Record the baseline. Keep the VM, image, task inputs, pool, prefetch, routing, and dependency limits fixed. Start with a conservative explicit concurrency value.
  3. Use a repeatable arrival pattern. Include normal traffic and a bounded burst. Run long enough to see whether backlog stabilizes and how it drains after the burst.
  4. Change only concurrency. Try a small increase that the CPU and memory budgets can support. Record task durations, useful completions, queue delay, CPU pressure, and peak memory.
  5. Repeat and compare. Stop increasing when throughput stops improving, delays worsen, failures rise, or memory headroom disappears. Recheck the chosen value with large valid inputs.
  6. Record the decision. Keep the configuration, software versions, input mix, test duration, observations, and rollback value together.

Multiple worker instances may help separate queues or operational responsibilities. Multiple VMs can add resources and separate failure boundaries, but they also add networking and deployment work. Compare those choices after identifying the bottleneck. Celery's pool autoscaling changes the number of execution processes within a worker; it does not provision more VMs or create extra CPU capacity.

Include retries and worker replacement in the capacity plan

Count attempts as well as new jobs. If a hypothetical minute brings 24 new jobs and 6 retry attempts, the worker handles 30 attempts during that minute, which is 25% more than the original arrival count. Retry attempts may have different durations, so measure their cost separately. For failed dependencies, use bounded retries, backoff, and operation timeouts appropriate to the task. Repeated side effects need an idempotency design; the task retry documentation covers the relevant behavior.

Plan worker replacement while jobs are active. Celery's warm shutdown on TERM waits for active work, but a container or host can impose a shorter termination deadline. Test the actual shutdown path with disposable jobs and reconcile their IDs afterward. A successful normal run does not establish recovery behavior. Leave capacity for reduced availability during maintenance, and keep the previous configuration available for rollback.

Bring workload evidence to a CPU VM decision

For a Hivenet CPU evaluation, bring the task types, arrival pattern, measured CPU and memory use, queue-delay requirement, dependency limits, and recovery expectations. The CPU documentation describes general-purpose and background-service uses; confirm current configurations and availability before planning deployment.

The CPU VM buying guide covers the surrounding resource and access questions. You can discuss a CPU workload with those requirements. This article provides a sizing method and a proposed experiment, with no measured Hivenet throughput, capacity, or savings claim.

‍

Your next workload belongs on Hivenet.

Pick one AI, compute, or storage workload and see the difference for yourself. Spin it up in minutes, or let our team map your fastest path to production.