Taming Memory Bloat with uWSGI Cheaper Subsystem
Learn how to use uWSGI's 'cheaper' subsystem to dynamically scale worker processes, reducing memory usage during idle periods while maintaining performance during traffic spikes.
20 Jun 2026, 10:42 UTC

The Cost of Idle Workers
In a standard uWSGI configuration, you define a fixed number of worker processes. If you set 16 workers to handle peak traffic, those 16 processes occupy system memory 24/7, even if your application spends 90% of its time idling. For Python applications with large dependency trees or heavy memory footprints, this "idle tax" can lead to unnecessary infrastructure costs or Out-Of-Memory (OOM) kills on smaller VPS instances.
The solution is the cheaper subsystem. Instead of a static pool, uWSGI can dynamically scale workers between a defined minimum and maximum based on actual request volume.
How the Cheaper Subsystem Operates
The cheaper subsystem acts as an adaptive manager for your worker pool. It requires two primary boundaries: cheaper (the floor) and processes (the ceiling). When load increases, uWSGI spawns additional workers up to the ceiling; when load drops, it kills off excess workers until it hits the floor.
The logic governing this movement is defined by the cheaper-algo. While there are several options, busyness is generally the most effective for web traffic. It tracks how much time workers spend actually processing requests versus sitting idle over a specific time window. If the average busyness exceeds a set threshold, uWSGI triggers a scale-up event.
Implementing Busyness-Based Scaling
To implement this, you must configure the scaling boundaries and the sensitivity of the busyness algorithm. The following configuration is designed for a memory-constrained environment where you want to maintain a small footprint but handle bursts efficiently.
# uwsgi.ini
[uwsgi]
module = myapp.wsgi:application
master = true
# Scaling Boundaries
processes = 16 # Maximum workers (the ceiling)
cheaper = 4 # Minimum workers (the floor)
# Scaling Logic
cheaper-algo = busyness
cheaper-busyness-max = 70 # Spawn worker if busyness > 70%
cheaper-busyness-min = 40 # Kill worker if busyness < 40%
cheaper-step = 2 # Spawn 2 workers at a time
cheaper-busyness-window = 5 # Look at the last 5 seconds
Deployment and Execution
Run this configuration as the uWSGI user (typically www-data or a dedicated app user) with the following command on your application server:
uwsgi --ini uwsgi.ini
Risk: Ensure the user running uWSGI has the necessary permissions to manage processes. If running under a systemd unit, ensure LimitNOFILE is set high enough to accommodate the maximum number of processes defined in processes.
Verifying the Scaling Behavior
You cannot verify dynamic scaling by looking at a static dashboard; you must observe the transition. Use a tool like Apache Benchmark (ab) to simulate a traffic spike:
- Baseline: Run
htoportop. You should see only 4 uWSGI worker processes running. - Stress Test: Run a high-concurrency test:
ab -n 1000 -c 50 http://your-app-url/. - Log Observation: Monitor the uWSGI master log. You should see entries indicating
spawnedworkers as the busyness threshold is crossed. - Cooldown: Stop the benchmark. After the
cheaper-busyness-windowexpires, observe the logs forkilledworker events as the pool shrinks back to 4.
Trade-offs and Limitations
Dynamic scaling is not a "silver bullet." The primary trade-off is latency spikes. When uWSGI spawns a new worker, that process must initialize the Python interpreter and load your application code into memory. This is known as a "cold start." If your application has a slow startup time (e.g., loading large ML models or complex ORM mappings), the first few requests that trigger a scale-up may experience significant delays.
Additionally, if cheaper-busyness-max is set too low, the system may enter a "flapping" state, where it constantly spawns and kills workers in rapid succession, consuming CPU cycles just to manage the process pool.
Decision Summary
Use the cheaper subsystem if your traffic is sporadic and your memory overhead per worker is high. If your traffic is consistently high or your application starts instantly, a fixed process pool is simpler and more performant. For most mid-sized Python deployments, starting with a busyness algorithm and a conservative cheaper-step provides the best balance between resource efficiency and responsiveness.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.