05 Jan 2015 Passenger Spawning Methods: Smart vs Direct
Passenger is one of the most popular application servers. It is not just limited to Ruby applications, but it also supports Python WSGI so it can run for example Flask and Bottle applications.
Passenger I/O Model
Passenger 4 uses the evented I/O model, just like Nginx. While the older versions used the multi-threaded model, which made passenger spawn the same number of threads as Apache. This caused a problem with the slow client connections; if a request was taking too long, other requests will be queued until some threads become available.
When passenger 3.2 was released, the I/O model was completely rewritten to use single threaded evented I/O model, which mitigate the problems of the multi-threaded model completely.
Passenger 5 codename (“raptor”) beta version was released several days ago, which include more features and uses the 3 I/O models.
Spawning Process
When Passenger needs to spawn new process, it loads the Ruby framework and the application with this process. This means that each process will load a full copy of the application in memory. This method of forking new process is called direct spawning (or conservative spawning in passenger 3.0).
The same method is used by Mongrel application server. Mongrel cluster creates a set of processes, each loads the full application in memory plus exec() a new ruby interpreter to be used by this process. Passenger works slightly different; it fork() a new process and reuse the same ruby interpreter code loaded in the memory. Yet, it still wastes a lot of memory, specially when having high traffic on the server.
Smart Spawn
Before version 4.0, Passenger used two variations of smart spawning:
– The first one is creating a Framework Spawner process (smart). This process caches the Ruby framework (e.g Rails) but not the code, and all the forked workers will share the same framework code.
– The other variation is smart-lv2 spawning, which loads both the application code and the framework. Since Passenger 4.0, smart-lv2 changed to be smart and the first level option removed.
Using this option, Passenger will be able to save memory by creating a preload process. The preload process loads the Ruby application code and the Ruby framework, so that when a new process is needed, the preloader process fork a new process that share the same application and the framework in memory.
Passenger uses OS feature called Copy On Write to optimize the sharing of the memory.
Copy On Write
Using cow in fork(), the child (worker process) will not copy the memory pages from the parent (preloader). Instead, it will share the same memory pages and mark these pages as copy-on-write. If it needs to modify any of this pages, a copy of this particular page is created for this child, adding a layer on top of these shared memory.
Pros and Cons
According to Passenger’s documentation, smart spawning will be 10 times faster than direct spawning, and will save also over 30% of the memory usage.
To set spawning method in Nginx to direct:
passenger_spawn_method direct;
When testing the application using direct spawning method, and checking the status of passenger using passenger-status:
* PID: 18727 Sessions: 1 Processed: 133 Uptime: 7m 40s CPU: 1% Memory : 124M Last used: 2m 11s ago * PID: 18750 Sessions: 1 Processed: 158 Uptime: 7m 33s CPU: 1% Memory : 124M Last used: 2m 22s ago
It shows that each process loads 124 MB of Ruby application and the Rails framework.
However when using smart spawning:
* PID: 19792 Sessions: 0 Processed: 61 Uptime: 45s CPU: 4% Memory : 84M Last used: 13s ago * PID: 19848 Sessions: 0 Processed: 38 Uptime: 16s CPU: 7% Memory : 71M Last used: 0s ago
One of the drawbacks of smart spawning is the sharing of the same file descriptor as the parent. This cause a problem that arises from the fork() system call, which in its syntax cause all the children to share the same socket as the preloader.
A popular example for this problem is using memcached with the rails application.
Memcached and Passenger Smart Spawn
When connecting the Ruby framework with memcached, the preloader process will share the same socket with the worker process. This will eventually cause errors, when the worker processes decide to do operations on memcached at the same time.
The solution to this problem is to instruct the application code to re-establish the connection with memcached for each newly forked worker.
Conclusion
One of the powerful features in passenger is the way it spawn new processes to handle new connections. Direct spawning load a full copy of the application to the newly created process, while smart spawning make the newly created processes share the same memory space.