From 39e840e43a78acc543c4a5f6f863a623155b21c7 Mon Sep 17 00:00:00 2001 From: Christian Gregg Date: Sat, 6 Mar 2021 11:15:50 +0000 Subject: [PATCH 1/4] Print warning when running one-worker cluster Running Puma in cluster-mode is likely a misconfiguration in most scenarios. Cluster mode has some overhead of running an addtional 'control' process in order to manage the cluster. If only running a single worker it is likely not worth paying that overhead vs running in single mode with additional threads instead. There are some scenarios where running cluster mode with a single worker may still be warranted and valid under certain deployment scenarios, see the linked issue for details. From experience at work, we were able to migrate our Rails applications from single worker cluster to single-mode and saw a reduction of RAM usage of around ~15%. Closes #2534 --- History.md | 1 + lib/puma/cluster.rb | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/History.md b/History.md index 4d841f59f8..15fc6d8e8b 100644 --- a/History.md +++ b/History.md @@ -2,6 +2,7 @@ * Features * Your feature goes here (#Github Number) + * Warn when running Cluster mode with a single worker (#2565) * Bugfixes * Your bugfix goes here (#Github Number) diff --git a/lib/puma/cluster.rb b/lib/puma/cluster.rb index 1c593d771f..9e5723d594 100644 --- a/lib/puma/cluster.rb +++ b/lib/puma/cluster.rb @@ -332,6 +332,13 @@ def run # This is aligned with the output from Runner, see Runner#output_header log "* Workers: #{@options[:workers]}" + if @options[:workers] == 1 + log "! WARNING: Detected running cluster mode with 1 worker:" + log "! Running Puma in cluster mode with a single worker is often a misconfiguration." + log "! Consider running Puma in single-mode in order to reduce memory overhead." + log "! See: https://github.com/puma/puma/issues/2534" + end + # Threads explicitly marked as fork safe will be ignored. # Used in Rails, but may be used by anyone. before = Thread.list.reject { |t| t.thread_variable_get(:fork_safe) } From d41f39280448db6fc9485e11e41cff1bc0db613d Mon Sep 17 00:00:00 2001 From: Christian Gregg Date: Sat, 6 Mar 2021 15:03:09 +0000 Subject: [PATCH 2/4] Remove link to issue --- lib/puma/cluster.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/puma/cluster.rb b/lib/puma/cluster.rb index 9e5723d594..aa82951d10 100644 --- a/lib/puma/cluster.rb +++ b/lib/puma/cluster.rb @@ -336,7 +336,6 @@ def run log "! WARNING: Detected running cluster mode with 1 worker:" log "! Running Puma in cluster mode with a single worker is often a misconfiguration." log "! Consider running Puma in single-mode in order to reduce memory overhead." - log "! See: https://github.com/puma/puma/issues/2534" end # Threads explicitly marked as fork safe will be ignored. From 3e5954a68b5acab3fa89694c90e1ddca1161b0bf Mon Sep 17 00:00:00 2001 From: Christian Gregg Date: Sun, 7 Mar 2021 18:34:39 +0000 Subject: [PATCH 3/4] Add #silence_single_worker_warning option --- lib/puma/configuration.rb | 1 + lib/puma/dsl.rb | 7 +++++++ test/test_config.rb | 16 ++++++++++++++++ 3 files changed, 24 insertions(+) diff --git a/lib/puma/configuration.rb b/lib/puma/configuration.rb index d7c18ad14e..a28777d63b 100644 --- a/lib/puma/configuration.rb +++ b/lib/puma/configuration.rb @@ -193,6 +193,7 @@ def puma_default_options :debug => false, :binds => ["tcp://#{DefaultTCPHost}:#{DefaultTCPPort}"], :workers => Integer(ENV['WEB_CONCURRENCY'] || 0), + :silence_single_worker_warning => false, :mode => :http, :worker_timeout => DefaultWorkerTimeout, :worker_boot_timeout => DefaultWorkerTimeout, diff --git a/lib/puma/dsl.rb b/lib/puma/dsl.rb index 45511e072e..b7761dbfc2 100644 --- a/lib/puma/dsl.rb +++ b/lib/puma/dsl.rb @@ -482,6 +482,13 @@ def workers(count) @options[:workers] = count.to_i end + # Disable warning message when running in cluster mode with a single worker. + # + # @note Cluster mode only. + def silence_single_worker_warning + @options[:silence_single_worker_warning] = true + end + # Code to run immediately before master process # forks workers (once on boot). These hooks can block if necessary # to wait for background operations unknown to Puma to finish before diff --git a/test/test_config.rb b/test/test_config.rb index 171e222672..072a3b3356 100644 --- a/test/test_config.rb +++ b/test/test_config.rb @@ -342,6 +342,22 @@ def test_final_options_returns_merged_options assert_equal 2, conf.final_options[:max_threads] end + def test_silence_single_worker_warning_default + conf = Puma::Configuration.new + conf.load + + assert_equal false, conf.options[:silence_single_worker_warning] + end + + def test_silence_single_worker_warning_overwrite + conf = Puma::Configuration.new do |c| + c.silence_single_worker_warning + end + conf.load + + assert_equal true, conf.options[:silence_single_worker_warning] + end + private def assert_run_hooks(hook_name, options = {}) From b52d994fe6fc3a286806dbace676655dfb06c3ed Mon Sep 17 00:00:00 2001 From: Christian Gregg Date: Sun, 7 Mar 2021 19:22:48 +0000 Subject: [PATCH 4/4] Test single_worker_warning --- lib/puma/cluster.rb | 17 +++++++++++------ test/test_integration_cluster.rb | 22 ++++++++++++++++++++++ 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/lib/puma/cluster.rb b/lib/puma/cluster.rb index aa82951d10..592160790d 100644 --- a/lib/puma/cluster.rb +++ b/lib/puma/cluster.rb @@ -332,12 +332,6 @@ def run # This is aligned with the output from Runner, see Runner#output_header log "* Workers: #{@options[:workers]}" - if @options[:workers] == 1 - log "! WARNING: Detected running cluster mode with 1 worker:" - log "! Running Puma in cluster mode with a single worker is often a misconfiguration." - log "! Consider running Puma in single-mode in order to reduce memory overhead." - end - # Threads explicitly marked as fork safe will be ignored. # Used in Rails, but may be used by anyone. before = Thread.list.reject { |t| t.thread_variable_get(:fork_safe) } @@ -388,6 +382,8 @@ def run log "Use Ctrl-C to stop" + single_worker_warning + redirect_io Plugins.fire_background @@ -476,6 +472,15 @@ def run private + def single_worker_warning + return if @options[:workers] != 1 || @options[:silence_single_worker_warning] + + log "! WARNING: Detected running cluster mode with 1 worker." + log "! Running Puma in cluster mode with a single worker is often a misconfiguration." + log "! Consider running Puma in single-mode in order to reduce memory overhead." + log "! Set the `silence_single_worker_warning` option to silence this warning message." + end + # loops thru @workers, removing workers that exited, and calling # `#term` if needed def wait_workers diff --git a/test/test_integration_cluster.rb b/test/test_integration_cluster.rb index e5bca38936..fc22588f69 100644 --- a/test/test_integration_cluster.rb +++ b/test/test_integration_cluster.rb @@ -313,6 +313,28 @@ def test_application_is_loaded_exactly_once_if_using_preload_app assert_equal 0, worker_load_count end + def test_warning_message_outputted_when_single_worker + cli_server "-w 1 test/rackup/hello.ru" + + output = [] + while (line = @server.gets) && line !~ /Worker \d \(PID/ + output << line + end + + assert_match /WARNING: Detected running cluster mode with 1 worker/, output.join + end + + def test_warning_message_not_outputted_when_single_worker_silenced + cli_server "-w 1 test/rackup/hello.ru", config: "silence_single_worker_warning" + + output = [] + while (line = @server.gets) && line !~ /Worker \d \(PID/ + output << line + end + + refute_match /WARNING: Detected running cluster mode with 1 worker/, output.join + end + private def worker_timeout(timeout, iterations, config)