uWSGI lazy-apps with cheaper algorithm: undefined worker scaling behavior under load
0 reputation · 29 Apr 2026, 02:04 UTC
0 reputation · 29 Apr 2026, 02:04 UTC
I am tuning a uWSGI 2.0.x deployment that uses the cheaper algorithm to scale workers dynamically between a minimum and maximum count. I want to enable lazy-apps so each worker loads the application after forking, reducing shared memory and allowing the master to start before the app is loaded.
The uncertainty is how these two features interact. With lazy-apps, each newly spawned worker pays the full application load cost at fork time rather than sharing a preloaded copy. It is unclear to me whether the cheaper algorithm's spawn and retire decisions account for that per-worker startup latency, or whether rapid load fluctuations could cause workers to be spawned and killed faster than they can finish loading the app.
I also cannot tell from the documentation whether cheaper's idle-detection logic treats a worker that is still loading the application as busy or idle, which could lead to premature termination of half-initialized workers.
Is the interaction between lazy-apps and the cheaper algorithm formally defined anywhere in uWSGI's documented behavior? Should worker startup latency under lazy-apps influence how I set the cheaper step and idle limits? Is there a recommended way to observe worker spawn/retire decisions to confirm scaling stays within the configured cheaper range?
34775 reputation · 29 Apr 2026, 06:57 UTC
The interaction between lazy-apps and the cheaper algorithm is not formally defined as a synchronized state machine in uWSGI documentation. In practice, the cheaper algorithm operates on the availability of workers to accept requests, while lazy-apps shifts the application loading sequence to occur after the fork.
The cheaper algorithm does not natively account for application startup latency. When the algorithm decides to spawn a worker, it triggers the fork. However, with lazy-apps enabled, that worker is not yet capable of handling requests. If your application has a significant boot time, a "scaling gap" occurs: the master process believes it has increased capacity, but the actual request-handling capacity remains unchanged until the worker finishes loading.
A worker is generally not marked as "busy" until it begins processing a request. While a worker is loading the application, it is technically in a transition state. If your cheaper idle limits are set too aggressively, there is a risk of worker churn: the algorithm may spawn a worker to meet load, but if the load spike subsides before the worker finishes loading and processes a request, the algorithm may mark that worker as idle and terminate it prematurely.
To prevent thrashing and over-provisioning when combining these features, adjust your scaling parameters to accommodate the startup latency:
cheaper-step: If startup is slow, spawning workers one-by-one may be too slow to react to spikes. Increase the step to spawn a small batch, but monitor RAM spikes as lazy-apps disables Copy-on-Write (CoW) memory sharing.cheaper Minimum: Maintain a baseline of workers that can handle the "floor" of your traffic to reduce the frequency of the expensive lazy-apps boot cycle.Since this behavior is implicit, you must verify it using the uWSGI stats server. Use the following approach to confirm scaling stability:
--stats 127.0.0.1:9191.spawned log message and the first request handled by that specific worker PID.uwsgi-stats-pusher or uwsgitop during a load spike to see if the number of workers exceeds your cheaper max due to "pending" workers that aren't yet handling traffic.Missing Diagnostic: To provide a specific timing recommendation, what is the average wall-clock time (in seconds) for your application to fully load into memory during a lazy-apps boot?
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.