# https://devcenter.heroku.com/articles/deploying-rails-applications-with-the-puma-web-server

# The environment variable WEB_CONCURRENCY may be set to a default value based
# on dyno size. To manually configure this value use heroku config:set
# WEB_CONCURRENCY.
workers Integer(ENV.fetch("WEB_CONCURRENCY", 3))
threads_count = Integer(ENV.fetch("MAX_THREADS", 5))
threads(threads_count, threads_count)

preload_app!

rackup DefaultRackup
environment ENV.fetch("RACK_ENV", "development")

on_worker_boot do
  # Worker specific setup for Rails 4.1+
  # See: https://devcenter.heroku.com/articles/deploying-rails-applications-with-the-puma-web-server#on-worker-boot
  ActiveRecord::Base.establish_connection
end

# https://devcenter.heroku.com/articles/deploying-rails-applications-with-the-puma-web-server

# The environment variable WEB_CONCURRENCY may be set to a default value based
# on dyno size. To manually configure this value use heroku config:set
# WEB_CONCURRENCY.
#
# Increasing the number of workers will increase the amount of resting memory
# your dynos use. Increasing the number of threads will increase the amount of
# potential bloat added to your dynos when they are responding to heavy
# requests.
#
# Starting with a low number of workers and threads provides adequate
# performance for most applications, even under load, while maintaining a low
# risk of overusing memory.
workers Integer(ENV.fetch("WEB_CONCURRENCY", 2))
threads_count = Integer(ENV.fetch("MAX_THREADS", 2))
threads(threads_count, threads_count)

preload_app!

rackup DefaultRackup
environment ENV.fetch("RACK_ENV", "development")

on_worker_boot do
  # Worker specific setup for Rails 4.1+
  # See: https://devcenter.heroku.com/articles/deploying-rails-applications-with-the-puma-web-server#on-worker-boot
  ActiveRecord::Base.establish_connection
end

Reduce number of Puma processes and threads
Reduce number of Puma processes and threads

Using a simple Suspenders application, I profiled memory usage for our
default configuration, as well as a few others. I found the following:

* A Puma cluster uses a master process and multiple worker processes.
  The amount of memory used by a cluster is equal to the memory usage of
  the master process plus the possible bloated size of a worker process
  times the number of worker processes.
* At boot, a simple Suspenders application uses about 117MB for the
  master process and 109M for each worker.
* After the first request is served, a process increases to around 117M,
  like the master process.
* The amount of potential bloat increases with each thread, because it's
  possible for every thread to be handling a bloated request at once.
* Using [siege], I determined that the expected bloat in a simple
  scenario is around 10M per thread. This will be much worse in some
  applications.

This provides the following formula for maximum memory usage under load:

    master_usage + worker_count * (worker_usage + bloat * thread_count)

For this simple Suspenders application, this formula provides the
following worst-case usage:

    117 + 3 * (117 + 10 * 5) = 618

This is over the 512MB limit for a 1x Heroku dyno, and the application
is very simple.

I recommend changing to a default of two worker processes and two
threads per dyno, changing the usage to:

    117 + 2 * (117 + 10 * 2) = 391

This provides reasonable performance with a high memory ceiling. When
applications begin to show troublesome performance characteristics under
load, developers can tune the application's process and thread count
according to its real-world memory usage, possibly upgrading the dyno
size as appropriate.

[siege]: https://www.joedog.org/siege-home/
