What it is
Puma is a concurrent HTTP server for Ruby, using a thread pool and optional worker processes, and is the default server for Rails.
Workers are processes, threads are concurrency within a worker. Correct sizing depends on available memory and the database connection pool.
Installation
gem 'puma'Getting started
The smallest useful thing you can do with it, and what each part means.
ruby
# config/puma.rb
max_threads = ENV.fetch('RAILS_MAX_THREADS', 5).to_i
threads max_threads, max_threads
workers ENV.fetch('WEB_CONCURRENCY', 2).to_i
preload_app! # required for copy-on-write memory savings
port ENV.fetch('PORT', 3000)
environment ENV.fetch('RAILS_ENV', 'production')
# Each forked worker needs its own connections.
on_worker_boot do
ActiveRecord::Base.establish_connection
end
before_fork do
ActiveRecord::Base.connection_pool.disconnect!
end
plugin :tmp_restartAdvanced usage
Where the library earns its place over a simpler alternative.
ruby
# Total concurrency = workers x threads.
# The database pool must cover the threads in ONE worker:
# pool: <%= ENV.fetch('RAILS_MAX_THREADS', 5) %>
# MRI has a GIL, so threads help with I/O-bound work, not CPU-bound.
# Start with threads 5 and workers = cores, then measure.
# Phased restart — zero downtime, workers replaced one at a time.
# pumactl phased-restart
# Note: it does not reload preloaded code, so it is unsuitable for
# deploys that change the application itself.
worker_timeout 60 # kill a worker that stops respondingErrors and fixes
The failures you are most likely to hit, and what actually resolves them.
- PG::ConnectionBad or corrupted responses after forking
- Connections were inherited across the fork. Disconnect in before_fork and reconnect in on_worker_boot.
- Requests queue while CPU is idle
- Threads are blocked on the connection pool. Raise the pool to match RAILS_MAX_THREADS.
Best practices
- Set the database pool to at least the thread count per worker.
- Use preload_app! with on_worker_boot to reconnect, or workers will share sockets.
- Size workers by memory and threads by I/O profile; measure rather than guessing.
- Set worker_timeout so a hung worker is replaced rather than blocking traffic.
Background
Why it exists, and what it was reacting to.
Puma replaced older single-threaded servers by using threads within workers, which suits Ruby's I/O-bound web workloads far better than one process per request.
