From 9d627dd7db215a6aec81b2ecb14db49bd54b5efd Mon Sep 17 00:00:00 2001 From: Schneems Date: Wed, 30 Jul 2025 09:53:01 -0400 Subject: [PATCH 1/2] Update keepalive timeout with Router 2.0 settings Router 2.0 introduced keepalive support. It will close connections idle after 90 seconds https://devcenter.heroku.com/articles/http-routing#keepalives. We want to avoid a situation where they send a request right before puma closes the connection. We can do that by setting it to the same timeout the router uses + some value (such as 5 seconds). This timeout is used in the reactor when buffering unfinished request bodies and waiting for new requests on a still open client connection. If a connection is closed incorrectly and doesn't send a TCP `FIN` this timeout will prevent puma holding onto that connection forever. There is currently a problem in puma 6x and prior with keepalives that causes high request variance https://github.com/puma/puma/issues/3487. So it's recommended that puma users disable keepalive connections by until that problem is fixed upstream: ``` $ heroku labs:enable http-disable-keepalive-to-dyno ``` --- config/puma.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/config/puma.rb b/config/puma.rb index a25d33bc..4dd61dfe 100644 --- a/config/puma.rb +++ b/config/puma.rb @@ -2,6 +2,9 @@ threads_count = ENV.fetch('RAILS_MAX_THREADS') { 5 } threads threads_count, threads_count +# Router keepalive idle timeout + 5 seconds +persistent_timeout(95) + preload_app! # Support IPv6 by binding to host `::` instead of `0.0.0.0` From 4f57f3ffd6e5fa3061c8d87382fa0d92a9678f05 Mon Sep 17 00:00:00 2001 From: Schneems Date: Wed, 30 Jul 2025 10:36:16 -0400 Subject: [PATCH 2/2] Disable keepalives by default This matches the current devcenter docs https://devcenter.heroku.com/articles/deploying-rails-applications-with-the-puma-web-server. --- config/puma.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/config/puma.rb b/config/puma.rb index 4dd61dfe..c6aacf8f 100644 --- a/config/puma.rb +++ b/config/puma.rb @@ -2,10 +2,14 @@ threads_count = ENV.fetch('RAILS_MAX_THREADS') { 5 } threads threads_count, threads_count +preload_app! + # Router keepalive idle timeout + 5 seconds persistent_timeout(95) -preload_app! +# Turn off keepalive support for better long tails response time with Router 2.0 +# Remove this line when https://github.com/puma/puma/issues/3487 is closed, and the fix is released +enable_keep_alives(false) if respond_to?(:enable_keep_alives) # Support IPv6 by binding to host `::` instead of `0.0.0.0` port(ENV.fetch("PORT") { 3000 }, "::")