To run Ruby in production environments, I’ll benchmark 3 of the widely used web-servers:
- Phusion Passenger (4.0.40)
- Unicorn (4.8.2)
- Puma (2.8.1)
These benchs are just for medium throughput applications, so I’m not comparing them with low load environments.
Both of them using the same nginx (1.4.7) build as a front web-server to handle the slow clients and the same Ruby version: 2.1.1. Running on a Ubuntu 12.04, 3.4ghz quad-core processor and 4gb of RAM.
Also, I made sure all of them were using the same machine resources:
And to run the benchmarks, I used Apache Benchmark tool with 200 concurrent connections.
ab -n 10000 -c 200 http://localhost:5000/
The first one I tried was Puma. I must say this was the easiest one. I ran the tests and no errors troubled me.
However, when I set up Unicorn, I faced several problems. The same problems happened using Passenger. The benchs were running too fast and returned many “non 20x responses”, so I did some research on what went wrong.
The first problem was that the nginx could’t hit the Unicorn socket due to “(11: Resource temporarily unavailable)”. I wasted a few hours and I finnaly get that the socket backlog was too low, therefore it was refusing new connections.
The solution is increasing the somaxconn directive in Kernel. We can do it changing the /etc/sysctl.conf file, on the line net.core.somaxconn=65535, then you run sysctl -w to update the setting without rebooting.
Also, be advised that in some cases you will have to increase your max open files as well.
But that’s a tricky tuning. You should only increase this value if your hardware can handle it. Doesn’t make sense increase this if your machine can’t handle it, thus making your app slow. Maybe you should scale horizontally instead. As Unicorn documentation says, If you’re doing extremely simple benchmarks and getting connection errors under high request rates, increasing your backlog above the already-generous default of 1024 can help avoid connection errors. Keep in mind this is not recommended for real traffic if you have another machine to failover to.
This is however, something I couldn’t understand. Why did Puma ran just fine without any tuning? No errors were thrown, it’s look like Puma manages the connection pool by itself, and keep incoming connections in the queue, I don’t know!
Similar problem were faced with Passenger. The tests ran too fast and the errors were something like: The queue is full. I just increased the queue size to 300 (default is 100). Again, this is not something you should do in production unless you know what you’re doing ;)
When I managed to resolve all these problems, I ran the benchmarks and… see by yourself.
I don’t think that performance is a reason to change your webserver. Consider other options like community, features and ease to use.
Today I run a large application with Passenger and there’s some reasons I want to change, and I’m happy to see that performance isn’t a concern anymore.
Also, I have to figure out why Puma works so seamlessly, while Passenger and Unicorn complained about the maximum socket connections. Is this a good thing? I’ll certainly share when I find out ;)