From 6ae17680473f7964262fcc4dc1a3cff750d8b8b6 Mon Sep 17 00:00:00 2001 From: Jeremy Prevost Date: Mon, 9 Feb 2026 15:39:21 -0500 Subject: [PATCH] Update Puma config Updates from Rails default to Heroku recommended Puma config. Also adds `rack-timeout` which is recommended by Heroku to ensure requests are actually stopped when they timeout. https://devcenter.heroku.com/articles/deploying-rails-applications-with-the-puma-web-server https://mitlibraries.atlassian.net/browse/USE-396 --- Gemfile | 1 + Gemfile.lock | 2 ++ config/puma.rb | 57 +++++++++++++++++++++++++------------------------- 3 files changed, 31 insertions(+), 29 deletions(-) diff --git a/Gemfile b/Gemfile index 2c512756..bb840a7b 100644 --- a/Gemfile +++ b/Gemfile @@ -13,6 +13,7 @@ gem 'mitlibraries-theme', git: 'https://github.com/mitlibraries/mitlibraries-the gem 'openssl' gem 'puma' gem 'rack-attack' +gem 'rack-timeout' gem 'rails', '~> 7.2.0' gem 'redis' gem 'scout_apm' diff --git a/Gemfile.lock b/Gemfile.lock index 37bb66c8..0b455b06 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -244,6 +244,7 @@ GEM rack (>= 3.0.0) rack-test (2.2.0) rack (>= 1.3) + rack-timeout (0.7.0) rackup (2.2.1) rack (>= 3) rails (7.2.2.2) @@ -426,6 +427,7 @@ DEPENDENCIES pg puma rack-attack + rack-timeout rails (~> 7.2.0) redis rubocop diff --git a/config/puma.rb b/config/puma.rb index 03c166f4..40c7d080 100644 --- a/config/puma.rb +++ b/config/puma.rb @@ -1,34 +1,33 @@ -# This configuration file will be evaluated by Puma. The top-level methods that -# are invoked here are part of Puma's configuration DSL. For more information -# about methods provided by the DSL, see https://puma.io/puma/Puma/DSL.html. +# Thread per process count allows context switching on IO-bound tasks for better CPU utilization. +threads_count = ENV.fetch('RAILS_MAX_THREADS') { 3 } +threads(threads_count, threads_count) -# Puma starts a configurable number of processes (workers) and each process -# serves each request in a thread from an internal thread pool. +# Processes count, allows better CPU utilization when executing Ruby code. +# Recommended to always run in at least one process so `rack-timeout` RACK_TERM_ON_TIMEOUT=1 can be used +# https://devcenter.heroku.com/articles/h12-request-timeout-in-ruby-mri +workers(ENV.fetch('WEB_CONCURRENCY') { 2 }) + +# Support IPv6 by binding to host `::` in production instead of `0.0.0.0` and `::1` instead of `127.0.0.1` in development. +host = ENV.fetch("RAILS_ENV") { "development" } == "production" ? "::" : "::1" + +# PORT environment variable is set by Heroku in production. +port(ENV.fetch("PORT") { 3000 }, host) + +# Allow Puma to be restarted by the `rails restart` command locally. +plugin(:tmp_restart) + +# Heroku strongly recommends upgrading to Puma 7+. If you cannot upgrade, +# Please see the Puma 6 and prior configuration section below. # -# The ideal number of threads per worker depends both on how much time the -# application spends waiting for IO operations and on how much you wish to -# to prioritize throughput over latency. +# Puma 7+ already supports PUMA_PERSISTENT_TIMEOUT natively. Older Puma versions set: # -# As a rule of thumb, increasing the number of threads will increase how much -# traffic a given process can handle (throughput), but due to CRuby's -# Global VM Lock (GVL) it has diminishing returns and will degrade the -# response time (latency) of the application. +# ``` +# persistent_timeout(ENV.fetch("PUMA_PERSISTENT_TIMEOUT") { 95 }.to_i) +# ``` # -# The default is set to 3 threads as it's deemed a decent compromise between -# throughput and latency for the average Rails application. +# Puma 7+ fixes a keepalive issue that affects long tail response time with Router 2.0. +# Older Puma versions set: # -# Any libraries that use a connection pool or another resource pool should -# be configured to provide at least as many connections as the number of -# threads. This includes Active Record's `pool` parameter in `database.yml`. -threads_count = ENV.fetch("RAILS_MAX_THREADS", 3) -threads threads_count, threads_count - -# Specifies the `port` that Puma will listen on to receive requests; default is 3000. -port ENV.fetch("PORT", 3000) - -# Allow puma to be restarted by `bin/rails restart` command. -plugin :tmp_restart - -# Specify the PID file. Defaults to tmp/pids/server.pid in development. -# In other environments, only set the PID file if requested. -pidfile ENV["PIDFILE"] if ENV["PIDFILE"] +# ``` +# enable_keep_alives(false) if respond_to?(:enable_keep_alives) +# ```