A Comparison of Popular Ruby Application Servers
blog.engineyard.com
blog.engineyard.com
> Unicorn is built to cut off long running worker processes after a default of 30 seconds via SIGKILL. This is why Unicorn says it's built for "fast clients" - because anything beyond that cut-off is subject to termination.
This is not what "fast clients" refers to. Unicorn being built for fast clients means that it is only meant to serve local network clients. Or, in other words, it's not meant to communicate with end-user connections/clients directly (slow clients), rather, it's meant to run behind a proxy server like nginx or apache.
The "fast client" thing is basically saying, "this is an application server, not a web server", meaning, it's good at running application code, not managing the complexities of serving web requests to public internet (slow) clients. They're called slow clients because, well, they're slow ;) They have variable quality connections, bandwidth / transfer rates, and client capabilities (http 1.0 vs 1.1, 2.0, spdy, etc). Being a fast client means you can expect that you'll receive http requests at a relatively constant rate, basically whatever the local network or file system will allow, which is extremely fast (comparatively).
So, unicorn being a "fast client" just means that it's good at talking over local network connections (or, preferably, unix sockets) with a web server, and never on port 80 serving public http traffic.
Also, rainbows! (http://rainbows.bogomips.org/) is a unicorn-based server for slow clients.
While arguably not popular enough (yet) to be considered in the comparison here, I have a feeling it will become a major contender as it matures.
If you're looking for high concurrency, or doing things like WebSockets/SSE, I'd recommend taking a look.
Is Rails (3.2/4.0) thread safe? Are most gems? For app code, presumably you'd have to avoid class variables and class instance variables. Anything else?
Rails 4 is. It turns on thread safe mode by default
> Are most gems?
Usually. I haven't had any problems with that myself
> For app code, presumably you'd have to avoid class variables and class instance variables
yeah thats quite a bit of it.
But yes, in Rails 4 that's the default mode, for Rails to allow concurrent overlapping requests. Meaning Rails 4 assumes by default that any local or gem code is thread-safe (may or may not be a safe assumption of course).
Both Rails 3.2 and Rails 4 (and really older Rails for quite some time) are intended and architected to be thread-safe. Modulo bugs. There were concurrency bugs in older versions for sure, which we know because so many of them have been fixed.
Rails have been thread-safe for quite some time (yehuda says after fall 2008, so even before the whole merb merger thing).
(see http://yehudakatz.com/2010/08/14/threads-in-ruby-enough-alre... comment section)
There really is such a huge performance advantage to a multi-threaded deploy environment -- EVEN with MRI, for the typical I/O-bound webapp, although you'd definitely want both multi-process and multi-threaded under MRI GIL. (Both puma and passenger enterprise can give you this, although not passenger free).
I'm hoping people start to catch on to this, and multi-threaded app servers get more popular, making them more mature and feature-rich, and in turn flushing out remaining bugs in Rails, causing yet more interest in multi-threaded deploy environment, virtuous circle blah blah
I'm not trying to nitpick, I just don't want anyone getting the impression this isn't true under jruby or rubinius.
This is not to say you shouldn't use those other app servers, or that Passenger always wins on performance, but I've always forced myself to ask the question: how will I benefit from the performance advantages of other app servers?
Another thing to keep in mind is that you have to look very carefully at your performance issues to understand whether your app server is actually your performance bottleneck. App servers get a lot of attention. There are droves of articles written comparing the maximum request rate achievable under circumstances that very few applications operate. The bottom line is that if your application spends most of its time executing Ruby code, your app server isn't your bottleneck. There is the possibility that you'll see an improvement in memory usage, but you need to test your application rather than rely on generalized assumptions. The improvements might not be worth the trade-offs.
1: https://www.phusionpassenger.com/documentation/Design%20and%...
I'm running a staging version of our Rails 3.8 app with MRI 2.1 and Puma in clustered/threaded mode and everything looks pretty good .. but I haven't been able to throw a ton of concurrent traffic at it yet. Potential thread safety issues give me the niggles, not quite ready to replace Unicorn on production just yet.
The really big thing is how just one puma worker with 8-16 threads can actually replace a set of about 6-8 unicorn/passenger worker instances and give you about the same level of performance.
That level of memory savings lets you use smaller boxes or put more puma workers on one box and eliminate others. Admittedly I'm not doing twitter request/minute numbers but I still was very pleased with my findings.
What continues to be the major issue with anything thread-based in ruby is that to reliably reach desired performance at load in a threaded setup, you need to be running an interpreter than can make concurrent use of native system threads. MRI has a global interpreter lock, so two pieces of ruby code will not run at the same time. IO is not subject to the GIL, so a lot of what happens in a web app can run concurrently (DB calls, etc), but other things that all happen in ruby (routing, view rendering) cannot.
So, basically, to reliably achieve similar performance using threads, you need to be using Rubinius or JRuby, which don't have a GIL.
> The second option, a global queue, allows Passenger to put all requests on the same queue "stack". Workers then are given whatever is next on this stack, thus making the aforementioned long request situation a little less problematic.
This is the default option out of the box and is actually the recommended mode of operation https://www.phusionpassenger.com/documentation/Users%20guide...
> workers will be killed off, and then when traffic picks up in the morning, requests will hang inside Passenger while it tries to launch new worker processes in memory. This also happens after nginx is restarted.
He mentions this later, in passing, but In the enterprise version of passenger, there is a mode where on restarting your app, passenger will not spin down worker processes until it has spuns up a new one. So this step wise system of spinning up a new worker before spinning down an old one ensures no-downtime deploys/restarts even after an nginx restart
Additionally, there is a configuration that will make Passenger not spin down instances at all (passenger_min_instances, just set it to be equal to passenger_max_instances). I'm a bit disappointed that this as seen as a downside for certain apps, as it's just a configuration that makes Passenger useful in some extra scenarios.
That said killing idle workers is the default configuration, and perhaps we should take this article as feedback that in current times that default is not what most people want anymore.
Passenger seems rock solid and always up whereas you have to have something monitoring the processes of the other two as they were liable to go down every now and again. Perhaps I just configured them incorrectly tho...
If you don't mind, please mail me.
And thanks for reaching out.
If you want a more complicated setup, for example with varnish and haproxy between nginx and your application servers, thin is rock solid. It runs Rails 2.x-4.x apps without any issues. It's also well suited for handling websockets traffic.
If the workload of your apps can take advantage of threads (e.g. lots of external API calls, or lots of time spent in the DB), a server like Puma will show an advantage. Also, if memory is limited, you can handle more concurrent requests.
Your use case is exactly what we built Phusion Passenger for. It could be that applying some configuration could improve startup performance for you, but general runtime performance of Passenger should be on par with a Unicorn solution.
Note that to use Unicorn you would have to configure an nginx instance that proxies for Unicorn. This is where Passenger shines, we already integrate with nginx so there's no extra configuration or management overhead. Passenger is resilient so it will survive any crashes of your application and automatically respawn any failed processes.
edit: The configuration to make your Passenger perfect for dedicated single app servers is:
passenger_min_instances your_num_of_cpus;
passenger_max_instances your_num_of_cpus;
You can measure your performance and tweak the number, as long as they are equal passenger won't be spawning and killing anymore, making your performance more reliable.Passenger works just fine with single-tenant dedicated instances. Just configure passenger_min_instances to the same number of max_instances or max_pool_size, and it'll behave exactly like Unicorn's default settings.