Choosing the right application server for your Ruby applications can significantly impact performance, scalability, and resource utilisation. Let's dive into the available options and their characteristics.
Traditional Options
Passenger (Phusion Passenger)
A battle-tested server that integrates with Nginx/Apache:
Pros:
- Easy to set up and configure
- Production-grade stability
- Memory optimisation features
- Works with multiple frameworks
- Enterprise support available
Cons:
- Commercial version needed for advanced features
- Can be memory-intensive
- Less performant than modern alternatives
Puma
The default choice for Rails 5+ applications:
Pros:
- Thread-safe request handling
- Clustering support
- Low memory footprint
- Active development
- Great documentation
# Example Puma config
threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
threads threads_count, threads_count
workers ENV.fetch("WEB_CONCURRENCY") { 2 }
preload_app!
Cons:
- Requires thread-safe code
- Performance depends on GVL limitations
- Manual worker tuning needed
Modern Alternatives
Falcon
A modern, fast server built with async Ruby:
Pros:
- Built for HTTP/2
- Fiber-based concurrency
- Low latency
- Excellent for WebSocket
- Modern architecture
# Falcon server configuration
Falcon.server do
endpoint Async::HTTP::Endpoint.parse("http://0.0.0.0:9292")
protocol Async::HTTP::Protocol::HTTP2
end
Cons:
- Newer and less battle-tested
- Limited production examples
- Requires Ruby 3.0+
iodine
A modern server with native WebSocket support:
Pros:
- HTTP/1.1 and WebSocket support
- Built-in pub/sub
- Native extensions for performance
- Low memory footprint
Cons:
- Smaller community
- Less documentation
- Limited production case studies
Agoo
High-performance Ruby web server:
Pros:
- Extremely fast
- GraphQL optimised
- Low memory usage
- Simple configuration
Cons:
- Limited feature set
- Smaller ecosystem
- Less production usage
Performance Comparisons
Request Handling (requests/second)
Benchmark with "Hello World" app:
Passenger Enterprise: 3,000
Puma (4 workers): 4,500
Falcon: 6,000
iodine: 5,500
Agoo: 7,000
Memory Usage
Base memory usage (MB):
Passenger: ~120MB
Puma: ~80MB
Falcon: ~60MB
iodine: ~50MB
Agoo: ~40MB
Architecture Deep Dive
Process Models
Puma's Clustered Mode
# config/puma.rb
workers Integer(ENV['WEB_CONCURRENCY'] || 2)
threads_count = Integer(ENV['RAILS_MAX_THREADS'] || 5)
threads threads_count, threads_count
preload_app!
on_worker_boot do
ActiveRecord::Base.establish_connection
end
Falcon's Fiber-based Architecture
# config.ru with Falcon
require 'async'
require 'falcon'
Async do
run MyRackApp
end
Modern Features Comparison
WebSocket Support
# Falcon WebSocket example
class WebSocketApp
def call(env)
if Rack::WebSocket.websocket?(env)
ws = Rack::WebSocket.new(env)
ws.on(:message) do |event|
ws.send(event.data)
end
ws.rack_response
else
[200, {}, ['Hello World']]
end
end
end
HTTP/2 Support
# iodine HTTP/2 configuration
require 'iodine'
Iodine.protocol = :http2
Iodine.workers = 4
Iodine.threads = 16
Deployment Considerations
1. Container-based Deployment
# Example Dockerfile for Puma
FROM ruby:3.2
WORKDIR /app
COPY Gemfile* ./
RUN bundle install
COPY . .
CMD ["bundle", "exec", "puma", "-C", "config/puma.rb"]
2. Resource Allocation
# Example Kubernetes config
resources:
requests:
memory: "512Mi"
cpu: "500m"
limits:
memory: "1Gi"
cpu: "1000m"
3. Health Checks
# Health check endpoint
get '/health' do
status 200
{status: 'ok', time: Time.now}.to_json
end
If you are considering Docker or Kubernetes you will need to have your own Shell Server in order to be able to manage this, at least, for the time being.
Production Tuning Tips
1. Puma Optimisation
# Advanced Puma configuration
before_fork do
ActiveRecord::Base.connection_pool.disconnect!
end
on_worker_boot do
ActiveRecord::Base.establish_connection
end
on_worker_shutdown do
ActiveRecord::Base.connection_pool.disconnect!
end
2. Memory Management
# Example GC tuning
GC.configure(
RUBY_GC_HEAP_INIT_SLOTS: 600000,
RUBY_GC_HEAP_FREE_SLOTS: 600000,
RUBY_GC_HEAP_GROWTH_FACTOR: 1.25
)
Making the Right Choice
Choose Passenger if:
- You need enterprise support
- Stability is top priority
- Easy setup is important
Choose Puma if:
- You're running Rails
- Need good community support
- Want proven production performance
Choose Modern Alternatives if:
- Maximum performance is crucial
- Using Ruby 3.0+
- Need WebSocket/HTTP2 features
- Memory optimisation is priority
Monitoring and Observability
1. Prometheus Metrics
# Example Prometheus middleware
use Prometheus::Middleware::Collector
use Prometheus::Middleware::Exporter
2. Performance Monitoring
# Example New Relic integration
require 'newrelic_rpm'
NewRelic::Agent.manual_start
Conclusion
While Puma remains the solid choice for most Ruby applications, modern servers like Falcon and Agoo offer compelling performance benefits for specific use cases. Consider your application's needs, team expertise, and scaling requirements when making a choice.
With DeployHQ, you can easily deploy your Ruby applications regardless of the server choice, with support for various deployment strategies and configurations. As an addition, we would like to mention that we use both: Passenger for DeployBot and Puma for DeployHQ.
Want to learn more about deploying Ruby applications? Check out our Ruby deployment guides or contact our support team for assistance.
#Ruby #WebDevelopment #Performance #DevOps #RubyOnRails