Use Puma to Deploy Rails Apps

Forward

WEBrick

The Ruby standard library has a default web server named WEBrick. It will be automatically installed into the system as Ruby is installed. Most development frameworks, such as Rack and Rails, use WEBrick as the web server for the development environment by default.

Although WEBrick greatly facilitates developers to debug programs during the development process, it cannot handle large-scale concurrent tasks, so other web servers need to be used instead of WEBrick in the production environment.

Why Can’t We Use WEBrick in a Production Environment

By default, WEBrick is single-task and single-threaded. This means that if two requests come at the same time, the second request must wait for the first request to be processed before it can be executed.

If the user does not specify a web server in the Procfile file, the platform will run WEBrick by default in the production environment. In this way, if your service is accessed by a large number of users, the user will feel that the speed is very slow, or it will time out and cannot be opened. , Even if the application level is expanded to 10 nodes, this problem cannot be solved.

Therefore, if you are running applications in the production environment on the cloud, you must replace the default web server.

Production Environment Web Server

The web server in the production environment should be able to handle large-scale concurrent tasks. We strongly recommend using Puma web server.

Users need to add puma to the Gemfile file, and then specify puma to run Ruby applications in the Procfile file, and finally submit the code and deploy it on the platform. Next, I will introduce puma to deploy Rails applications.

Puma Deploy Rails Application

Puma is a strong competitor of Unicorn, and they can handle large-scale concurrent requests.

In addition to worker processes, Puma uses threads to process requests by default, which can make fuller use of CPU resources. But this requires that all the user’s code must be thread-safe, even if it is not, it does not matter, users can still achieve the purpose of processing high concurrent requests by horizontally (horizontally) extending the worker process.

This document will guide you step by step to deploy Rails applications to Cloud Help using puma web server.

Before deploying the program to the production environment, make sure that it can run normally in the test environment.

Add Puma to the Program

Gemfile File

First, add puma to the application’s Gemfile file:

echo "gem'puma'" >> Gemfile

Install puma locally

bundler install

Procfile File

Using Puma as the application’s web handler, users can set multiple parameters in one line:

web: bundle exec puma -t 5:5 -p ${PORT:-3000} -e ${RACK_ENV:-development}

Under normal circumstances, we recommend saving the puma configuration as a configuration file:

web: bundle exec puma -C config/puma.rb

Please make sure that the content of the Procfile is correct and add it to the git code repository.

Configuration File

You can save the puma configuration file in config/puma.rb or a location you like. Here is a simple Rails application configuration file:

workers Integer(ENV['WEB_CONCURRENCY'] || 2)
threads_count = Integer(ENV['MAX_THREADS'] || 5)

# If the program is not thread-safe, you need to set threads_count to 1
# threads_count = 1

threads threads_count, threads_count

preload_app!

rackup DefaultRackup
port ENV['PORT'] || 3000
environment ENV['RACK_ENV'] ||'development'

on_worker_boot do
  # Worker specific setup for Rails 4.1+
  # See: http://docs.gridworkz.com/ruby/rails-puma.html#On_worker_boot
  ActiveRecord::Base.establish_connection
end

The user needs to confirm that the correct database connection is configured and can connect to the database normally. The following will introduce how to set up a database connection.

Workers

workers Integer(ENV['WEB_CONCURRENCY'] || 2)

WEB_CONCURRENCY Environment variables are set according to the memory size of a single instance of the application. The default In this case, a 128 memory application instance defaults to WEB_CONCURRENCY=2 At present, the memory of an instance of Gridworkz is fixed at 128M, and custom adjustments will be supported in the future. At the same time, the WEB_CONCURRENCY variable will be adjusted as the memory is adjusted.

Threads

threads_count = Integer(ENV['MAX_THREADS'] || 5)
threads threads_count, threads_count

Puma can hand over requests to an internal thread pool for processing, which can handle more concurrent requests, reduce response time, and at the same time occupy less memory and maximize the use of CPU resources.

Puma allows users to set the maximum and minimum limits on the number of threads, and the dynamic adjustment operation is completed by the Puma master instance. The minimum number of threads means the minimum number of threads when there are no requests, and the maximum means the maximum number of threads that can be opened after the amount of requests comes up. The default is 0:16, which is the minimum 0 and the maximum 16, which can be set freely by the user. We recommend setting the maximum and minimum values ​​to be the same, so that requests can be processed efficiently without wasting CPU resources by dynamically adjusting the number of threads.

Preload App

preload_app!

The pre-loaded application reduces the startup time of a single Puma worker processing process, and you can use an independent worker to use the on_worker_boot method to manage external connections. This configuration can ensure that each worker process can use an elegant way to connect to the database.

Rackup

rackup DefaultRackup

Use the rackup command to tell Puma how to start your rack application. This configuration is automatically written to the config.ru file by Rails by default. Therefore, subsequent Puma may not need to be set

Port

port ENV['PORT'] || 3000

After the cloud help application is started, the PORT variable is automatically set so that the application can be added to the load balancer. The local default setting is 3000, which is also the Rails default value.

Environment

environment ENV['RACK_ENV'] ||'development'

Set Puma’s environment variables, and the Rails application running on it will set ENV[‘RACK_ENV’] to’production' by default

On Worker Boot

The definition part of on_worker_boot is the part that is executed before the request is accepted after the worker is started. Mainly to solve the problem of database connection concurrency, if the user is using Rails 4.1+, you can directly set the connection pool size in database.yml to solve it.

on_worker_boot do
  # Valid on Rails 4.1+ using the `config/database.yml` method of setting `pool` size
  ActiveRecord::Base.establish_connection
end

Otherwise, you must make specific settings for this part:

on_worker_boot do
  # Valid on Rails up to 4.1 the initializer method of setting `pool` size
  ActiveSupport.on_load(:active_record) do
    config = ActiveRecord::Base.configurations[Rails.env] ||
                Rails.application.config.database_configuration[Rails.env]
    config['pool'] = ENV['MAX_THREADS'] || 5
    ActiveRecord::Base.establish_connection(config)
  end
end

By default we all need to set the database connection pool size

If other data storage is used in the program, such as redis, memcached, postgres, etc., it is also necessary to set the reconnection mechanism of activity records in the pre-load area. If you use Resque to connect to Redis, you need to use the reconnection mechanism:

on_worker_boot do
  # ...
  if defined?(Resque)
     Resque.redis = ENV["<redis-uri>"] || "redis://127.0.0.1:6379"
  end
end

Thread Safety

Thread-safe code can run error-free on multiple threads. Not all Ruby code is thread-safe. If your code and library are running in multiple threads, it is difficult to detect whether it is thread-safe.

Until Rails 4, there has been a thread-safe compatibility mode that can be automatically switched. Although Rails is thread-safe, there is no guarantee that the user’s code will be too. If your program has not been run in a multi-threaded environment, we recommend that you set the maximum and minimum threads to 1 at the same time during deployment, that is, disable thread mode

threads_count = 1
threads threads_count, threads_count

Even if thread mode is disabled, users can still adjust the number of workers to increase processing power. Each process uses independent memory, even if the code is not thread-safe, it can be processed across multiple processes.

Database Connectivity

When the number of threads or processes is adjusted, the connection of the program to the database will also increase. When a loss or intermittent database connection is detected, the database connection pool size needs to be adjusted. When a Rails application encounters a full connection pool, the following error will be reported:

ActiveRecord::ConnectionTimeoutError-could not obtain a database connection within 5 seconds

This means that Rails’s connection pool is not set reasonably

Deploy to cloud help

Add puma to Gemfile:
echo "gem'puma'" >> Gemfile

# Install puma locally and regenerate the Gemfile.lock file
bundler install
puma.ru Configuration File
$ cat ./config/puma.rb

workers Integer(ENV['WEB_CONCURRENCY'] || 2)
threads_count = Integer(ENV['MAX_THREADS'] || 5)

# If the program is not thread-safe, you need to set threads_count to 1
# threads_count = 1

threads threads_count, threads_count

preload_app!

rackup DefaultRackup
port ENV['PORT'] || 3000
environment ENV['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
Fill in the Procfile
echo "web: bundle exec puma -C config/puma.rb"> ./Procfile
Submit Code
git add.
git commit -m "add puma"
git push origin master