Skip to content

Commit

Permalink
Print warning when running one-worker cluster (puma#2565)
Browse files Browse the repository at this point in the history
* 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 puma#2534

* Remove link to issue

* Add #silence_single_worker_warning option

* Test single_worker_warning
  • Loading branch information
CGA1123 authored and JuanitoFatas committed Sep 9, 2022
1 parent 2776719 commit de23adb
Show file tree
Hide file tree
Showing 6 changed files with 58 additions and 0 deletions.
1 change: 1 addition & 0 deletions History.md
Expand Up @@ -2,6 +2,7 @@

* Features
* Your feature goes here <Most recent on the top, like GitHub> (#Github Number)
* Warn when running Cluster mode with a single worker (#2565)

* Bugfixes
* Your bugfix goes here <Most recent on the top, like GitHub> (#Github Number)
Expand Down
11 changes: 11 additions & 0 deletions lib/puma/cluster.rb
Expand Up @@ -382,6 +382,8 @@ def run

log "Use Ctrl-C to stop"

single_worker_warning

redirect_io

Plugins.fire_background
Expand Down Expand Up @@ -470,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
Expand Down
1 change: 1 addition & 0 deletions lib/puma/configuration.rb
Expand Up @@ -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,
Expand Down
7 changes: 7 additions & 0 deletions lib/puma/dsl.rb
Expand Up @@ -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
Expand Down
16 changes: 16 additions & 0 deletions test/test_config.rb
Expand Up @@ -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 = {})
Expand Down
22 changes: 22 additions & 0 deletions test/test_integration_cluster.rb
Expand Up @@ -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)
Expand Down

0 comments on commit de23adb

Please sign in to comment.