將 Rails 伺服器從 unicorn 切換到 puma
在 rails 應用中,常用的兩種流行 Web 伺服器中,unicorn 使用工作進程來處理請求,而 puma 則同時使用進程工作器和執行緒來處理請求。因此,從 unicorn 切換到 puma 不僅可以幫助提高並發性,還可以減少記憶體使用量。另外,自 rails 5.0 以來,預設使用的 Web 伺服器是 puma。
Prerequisites
In order to use puma for rails app, we must make sure the code is thread safe. Although the way how rails framework was designed to serve request by instantializing a new controller object per request makes it safe under multiple threads environment, there are still many things to check and update, dependent on specific apps.
-
Check the
lib
folder
Thelib
folder usually contains code used by adhoc scripts or batch jobs that is not used by MVC code, but legacy apps tend to add thelib
folder or its subfolders intoautoload_paths
with eager loading and cache class turned off (in our case), so we need refactor them first to:- Move code in
lib
that is used by MVC code toapp/lib
- Leave only adhoc script related code in the
lib
folder
- Move code in
-
Check configuration about eager loading and cache classes
Set the values based on recommendation:
# for production/preprod
config.eager_load = true
config.cache_classes = true
# for development
config.eager_load = false
config.cache_classes = false
# for test
config.eager_load = false
config.cache_classes = true
# For all environments
# Do not change or mess with autoload_paths because it's not necessary
-
Search for
@@
about class variables usage
Search for class variables in code base, and even if they are read only after app finishes loading, most of the time, they should not be used and can be replaced by constants. -
Search for
||=
about memoization usage on class instance variables
The||=
on class instance variables should be avoided, such as:
class << self
def config
@config ||= load_config
end
end
should be guarded with a mutex block like:
@mutex = Mutex.new
class << self
def config
@config ||= @mutex.synchronize do
@config ||= load_config
end
end
end
- Maybe some others depending on app...
Switch to use puma for default server
- Update
Gemfile
For legacy app using unicorn, theGemfile
andGemfile.lock
need updated:
gem 'unicorn' # remove this
gem 'puma' # add this
and run bundle i
to update Gemfile.lock
- Prepare environment specific puma configuration
Createconfig/puma/{environment}.rb
config files to adjust config based on environment, for example:
port ENV.fetch('PORT') { 3000 }
environment ENV.fetch('RAILS_ENV') { 'development' }
bind "unix://#{shared_path}/tmp/sockets/puma.sock"
environment ENV.fetch('RAILS_ENV') { 'production' }
- Update deploy scripts to deploy rails app with puma instead of unicorn
If you use capistrano for deployment, a gem called capistrano3-puma makes it easy to deploy with puma but it can also be problematic, such as the latest version of puma (5.0) is not yet supported as of this writing. In such case, you can write custom script to use tools likesystemd
to manage the puma services on linux servers.
參考文獻
- https://github.com/rails/rails/issues/33209
- https://stackoverflow.com/questions/15184338/how-to-know-what-is-not-thread-safe-in-ruby/15184752#15184752
- https://devcenter.heroku.com/articles/deploying-rails-applications-with-the-puma-web-server#thread-safety
- https://bearmetal.eu/theden/how-do-i-know-whether-my-rails-app-is-thread-safe-or-not/
- https://guides.rubyonrails.org/v5.1/autoloading_and_reloading_constants.html
- https://github.com/puma/puma/blob/master/docs/deployment.md
- https://medium.com/@anilkumarmaurya/when-not-to-use-memoization-in-ruby-on-rails-9d54bce0ae74
- https://github.com/rails/rails/pull/9789
- https://blog.arkency.com/3-ways-to-make-your-ruby-object-thread-safe/
- https://stackoverflow.com/questions/62458471/is-establish-connection-on-worker-boot-still-required-on-rails-6-and-puma
- https://github.com/puma/puma/issues/1001