Ruby has many application servers that are widely used in production. In this blog post, I would like to discuss the five application servers below:
- Unicorn
- Phusion Passenger
- Puma
- Thin
- Falcon
This article requires an understanding of the following topics:
- Why does Ruby require application servers?
- What is the Global Interpreter Lock (GIL)? - GIL
- The differences between Parallelism and Concurrency. You can refer to the links below: Parallel computing Concurrent computing
Unicorn
Unicorn is one of the popular open-source application servers available for Ruby on Rails.
The Unicorn spawner uses forking to handle multiple requests. If you are running your application on a multi-core system, you can achieve parallelism by configuring the process count in Unicorn.
As this is process-based, memory consumption per server instance will be higher. For example, if your Ruby process takes around 10 MB and you are running 4 workers (processes), your default memory consumption per server instance will be approximately 40 MB.
However, if you use Ruby 2.0 or higher, forking is possible with less memory consumption due to the Copy-on-Write (CoW) advantage of the operating system. You need to ensure that you are reconnecting to the database, Redis, and the file system after forking.
Unicorn does not provide multi-threaded capability for handling requests.
Phusion Passenger
There are two versions of Phusion Passenger available:
- Community Edition (Open Source)
- Enterprise Edition
In the Community Edition, Passenger can be configured as a multi-process server (fork-based). The description for Unicorn above also applies to Passenger in this mode.
In the Enterprise Edition, Passenger can be configured as both a multi-process and a multi-threaded server.
In a multi-threaded server using MRI as the runtime, you will not be able to achieve true parallelism due to the Global Interpreter Lock (GIL). Because of the GIL, MRI does not allow threads to execute in parallel even on a multi-core system. However, you can achieve concurrency across requests. If your application is IO-bound, configuring it as multi-threaded instead of multi-process will improve your application’s memory footprint.
If you use a multi-threaded server with the JRuby runtime, you can achieve true parallelism. JRuby and Rubinius runtimes do not have a GIL implementation.
Note that you must ensure your code is thread-safe if you configure your application server to be multi-threaded. Read about thread safety in Ruby here
Passenger also supports other applications like Python and Node.js. This is one of the advantages of using Passenger over Unicorn or Puma.
Puma
Puma is a favorite of Heroku and is now the default application server for Rails. It is open-source and free software.
Puma provides both multi-threaded and multi-process server capabilities for free. This is a significant advantage when using Puma over Passenger or Unicorn for Ruby applications.
Thin
Thin is a single-threaded concurrent web application server. It uses EventMachine (event-driven) internally to gain concurrency. Any IO calls will not block the main thread that receives the requests.
Falcon
Falcon is fiber-based instead of thread-based. Fibers in Ruby consume significantly less memory than threads. Consequently, it often has a much smaller memory footprint than other application servers.