Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Switch web server from Thin to Unicorn #172

Closed
wants to merge 3 commits into from

5 participants

@croaky
Admin

As the Rails application scales to more Heroku dynos, a lack of concurrency
can result in poor performance and an over-provisioning of resources.
Modifying the app to handle requests more efficiently is a simple process
with immediate benefits.

Unicorn is a concurrent web server that spawns several processes within
a single dyno without requiring concurrency or threading awareness in your
app. Applications that migrate to Unicorn often require fewer dynos and see
increased performance.

The config/unicorn.rb file specifies the number of concurrent web
processes to run on each dyno as well as the proper forking and termination
behavior.

[1] https://blog.heroku.com/archives/2013/2/16/routing_performance_update
[2] https://blog.heroku.com/archives/2013/2/27/unicorn_rails
[3]
https://devcenter.heroku.com/articles/rails-unicorn#adding-unicorn-to-your-application

@croaky croaky Switch web server from Thin to Unicorn
As the Rails application scales to more Heroku dynos, a lack of
concurrency can result in [poor performance](1) and an over-provisioning
of resources. Modifying the app to handle requests more efficiently is
a simple process with immediate benefits.

Unicorn is a [concurrent web server](2) that spawns several processes
within a single dyno without requiring concurrency or threading
awareness in your app.  Applications that migrate to Unicorn often
require fewer dynos and see increased performance.

The [config/unicorn.rb file](3) specifies the number of concurrent web
processes to run on each dyno as well as the proper forking and
termination behavior.

[1] https://blog.heroku.com/archives/2013/2/16/routing_performance_update
[2] https://blog.heroku.com/archives/2013/2/27/unicorn_rails
[3] https://devcenter.heroku.com/articles/rails-unicorn#adding-unicorn-to-your-application
d4dfb84
@harlow harlow commented on the diff
templates/unicorn.rb
@@ -0,0 +1,24 @@
+worker_processes (ENV['WEB_CONCURRENCY'] || 3).to_i
+timeout (ENV['WEB_TIMEOUT'] || 15).to_i
+preload_app true
+
+before_fork do |server, worker|
+ Signal.trap 'TERM' do
+ puts 'Unicorn master intercepting TERM and sending myself QUIT instead'
@harlow
harlow added a note

Is there a reason for puts over Rails.logger.info? I'm not sure if it makes any difference in the console output.

@mike-burns Admin

Do we have access to Rails in here?

@croaky Admin
croaky added a note
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@harlow

This looks good Dan. :thumbsup:

templates/unicorn.rb
@@ -0,0 +1,24 @@
+worker_processes (ENV['WEB_CONCURRENCY'] || 3).to_i
+timeout (ENV['WEB_TIMEOUT'] || 15).to_i
+preload_app true
+
+before_fork do |server, worker|
+ Signal.trap 'TERM' do
@jferris Admin
jferris added a note

There's a decent amount of code in here, and I'm a little nervous about generating it into all our applications. We'd also need to copy/paste to get this in existing applications.

What do you think about making a small, new gem (I like the name "fork_you"), which just handles the concern of "what do I need to do when dealing with a forking server?"

It could also be useful for abstracting the forking concept. Currently, you'd need different hooks for forking web servers (Unicorn/Passenger) and forking job processors (Delayed Job/Resque).

If we like this idea, it may make sense as a follow-up to this commit, rather than blocking the merge.

@mjankowski Admin

Maybe add a comment line pointing to an official heroku source for this? Blog post, template file, whatever.

https://github.com/heroku/ruby-rails-unicorn-sample/blob/master/config/unicorn.rb

So that this comes off as "oh, this is actually heroku's official recommendation for unicorn setup" and not "this is some random ruby/forking/whatever that we hacked together"...

@croaky Admin
croaky added a note

@mjankowski Good call. Done in 99eaf5d.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@croaky croaky closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Apr 5, 2013
  1. @croaky

    Switch web server from Thin to Unicorn

    croaky authored
    As the Rails application scales to more Heroku dynos, a lack of
    concurrency can result in [poor performance](1) and an over-provisioning
    of resources. Modifying the app to handle requests more efficiently is
    a simple process with immediate benefits.
    
    Unicorn is a [concurrent web server](2) that spawns several processes
    within a single dyno without requiring concurrency or threading
    awareness in your app.  Applications that migrate to Unicorn often
    require fewer dynos and see increased performance.
    
    The [config/unicorn.rb file](3) specifies the number of concurrent web
    processes to run on each dyno as well as the proper forking and
    termination behavior.
    
    [1] https://blog.heroku.com/archives/2013/2/16/routing_performance_update
    [2] https://blog.heroku.com/archives/2013/2/27/unicorn_rails
    [3] https://devcenter.heroku.com/articles/rails-unicorn#adding-unicorn-to-your-application
Commits on Apr 6, 2013
  1. @croaky

    Add DevCenter article to Unicorn config

    croaky authored
    Lower timeout to 5 seconds.
  2. @croaky
This page is out of date. Refresh to see the latest.
View
2  Gemfile
@@ -1,3 +1,3 @@
-source "http://rubygems.org"
+source 'https://rubygems.org'
gemspec
View
121 Gemfile.lock
@@ -2,19 +2,19 @@ PATH
remote: .
specs:
suspenders (1.2.2)
- bundler (>= 1.1)
- hub (~> 1.10.2)
- rails (= 3.2.12)
+ bundler (~> 1.3)
+ hub (~> 1.10)
+ rails (= 3.2.13)
GEM
- remote: http://rubygems.org/
+ remote: https://rubygems.org/
specs:
- actionmailer (3.2.12)
- actionpack (= 3.2.12)
- mail (~> 2.4.4)
- actionpack (3.2.12)
- activemodel (= 3.2.12)
- activesupport (= 3.2.12)
+ actionmailer (3.2.13)
+ actionpack (= 3.2.13)
+ mail (~> 2.5.3)
+ actionpack (3.2.13)
+ activemodel (= 3.2.13)
+ activesupport (= 3.2.13)
builder (~> 3.0.0)
erubis (~> 2.7.0)
journey (~> 1.0.4)
@@ -22,51 +22,49 @@ GEM
rack-cache (~> 1.2)
rack-test (~> 0.6.1)
sprockets (~> 2.2.1)
- activemodel (3.2.12)
- activesupport (= 3.2.12)
+ activemodel (3.2.13)
+ activesupport (= 3.2.13)
builder (~> 3.0.0)
- activerecord (3.2.12)
- activemodel (= 3.2.12)
- activesupport (= 3.2.12)
+ activerecord (3.2.13)
+ activemodel (= 3.2.13)
+ activesupport (= 3.2.13)
arel (~> 3.0.2)
tzinfo (~> 0.3.29)
- activeresource (3.2.12)
- activemodel (= 3.2.12)
- activesupport (= 3.2.12)
- activesupport (3.2.12)
- i18n (~> 0.6)
+ activeresource (3.2.13)
+ activemodel (= 3.2.13)
+ activesupport (= 3.2.13)
+ activesupport (3.2.13)
+ i18n (= 0.6.1)
multi_json (~> 1.0)
arel (3.0.2)
- aruba (0.4.11)
- childprocess (>= 0.2.3)
+ aruba (0.5.1)
+ childprocess (~> 0.3.6)
cucumber (>= 1.1.1)
- ffi (>= 1.0.11)
- rspec (>= 2.7.0)
+ rspec-expectations (>= 2.7.0)
builder (3.0.4)
- childprocess (0.3.6)
- ffi (~> 1.0, >= 1.0.6)
- cucumber (1.1.9)
+ childprocess (0.3.9)
+ ffi (~> 1.0, >= 1.0.11)
+ cucumber (1.2.3)
builder (>= 2.1.2)
- diff-lcs (>= 1.1.2)
- gherkin (~> 2.9.0)
- json (>= 1.4.6)
- term-ansicolor (>= 1.0.6)
- diff-lcs (1.1.3)
+ diff-lcs (>= 1.1.3)
+ gherkin (~> 2.11.6)
+ multi_json (~> 1.3)
+ diff-lcs (1.2.2)
erubis (2.7.0)
- ffi (1.3.1)
- gherkin (2.9.3)
- json (>= 1.4.6)
+ ffi (1.6.0)
+ gherkin (2.11.6)
+ json (>= 1.7.6)
hike (1.2.1)
- hub (1.10.4)
- i18n (0.6.4)
+ hub (1.10.5)
+ i18n (0.6.1)
journey (1.0.4)
- json (1.7.6)
- mail (2.4.4)
+ json (1.7.7)
+ mail (2.5.3)
i18n (>= 0.4.0)
mime-types (~> 1.16)
treetop (~> 1.4.8)
- mime-types (1.21)
- multi_json (1.7.0)
+ mime-types (1.22)
+ multi_json (1.7.2)
polyglot (0.3.3)
rack (1.4.5)
rack-cache (1.2)
@@ -75,40 +73,33 @@ GEM
rack
rack-test (0.6.2)
rack (>= 1.0)
- rails (3.2.12)
- actionmailer (= 3.2.12)
- actionpack (= 3.2.12)
- activerecord (= 3.2.12)
- activeresource (= 3.2.12)
- activesupport (= 3.2.12)
+ rails (3.2.13)
+ actionmailer (= 3.2.13)
+ actionpack (= 3.2.13)
+ activerecord (= 3.2.13)
+ activeresource (= 3.2.13)
+ activesupport (= 3.2.13)
bundler (~> 1.0)
- railties (= 3.2.12)
- railties (3.2.12)
- actionpack (= 3.2.12)
- activesupport (= 3.2.12)
+ railties (= 3.2.13)
+ railties (3.2.13)
+ actionpack (= 3.2.13)
+ activesupport (= 3.2.13)
rack-ssl (~> 1.3.2)
rake (>= 0.8.7)
rdoc (~> 3.4)
thor (>= 0.14.6, < 2.0)
- rake (10.0.3)
+ rake (10.0.4)
rdoc (3.12.2)
json (~> 1.4)
- rspec (2.12.0)
- rspec-core (~> 2.12.0)
- rspec-expectations (~> 2.12.0)
- rspec-mocks (~> 2.12.0)
- rspec-core (2.12.2)
- rspec-expectations (2.12.1)
- diff-lcs (~> 1.1.3)
- rspec-mocks (2.12.1)
+ rspec-expectations (2.13.0)
+ diff-lcs (>= 1.1.3, < 2.0)
sprockets (2.2.2)
hike (~> 1.2)
multi_json (~> 1.0)
rack (~> 1.0)
tilt (~> 1.1, != 1.3.0)
- term-ansicolor (1.0.7)
- thor (0.17.0)
- tilt (1.3.5)
+ thor (0.18.1)
+ tilt (1.3.6)
treetop (1.4.12)
polyglot
polyglot (>= 0.3.1)
@@ -118,6 +109,6 @@ PLATFORMS
ruby
DEPENDENCIES
- aruba (~> 0.4.11)
- cucumber (~> 1.1.9)
+ aruba (~> 0.5)
+ cucumber (~> 1.2)
suspenders!
View
4 lib/suspenders/app_builder.rb
@@ -195,6 +195,10 @@ def generate_clearance
generate 'clearance:install'
end
+ def configure_unicorn
+ copy_file 'unicorn.rb', 'config/unicorn.rb'
+ end
+
def setup_foreman
copy_file 'sample.env', '.sample.env'
copy_file 'Procfile', 'Procfile'
View
1  lib/suspenders/generators/app_generator.rb
@@ -120,6 +120,7 @@ def configure_app
build :disable_xml_params
build :add_email_validator
build :setup_default_rake_task
+ build :configure_unicorn
build :setup_foreman
end
View
41 suspenders.gemspec
@@ -1,37 +1,32 @@
# -*- encoding: utf-8 -*-
-$:.push File.expand_path("../lib", __FILE__)
+$:.push File.expand_path('../lib', __FILE__)
require 'suspenders/version'
require 'date'
Gem::Specification.new do |s|
- s.name = 'suspenders'
- s.version = Suspenders::VERSION
- s.date = Date.today.strftime('%Y-%m-%d')
- s.authors = ['thoughtbot']
- s.email = 'support@thoughtbot.com'
- s.homepage = 'http://github.com/thoughtbot/suspenders'
+ s.add_dependency 'bundler', '~> 1.3'
+ s.add_dependency 'hub', '~> 1.10'
+ s.add_dependency 'rails', '3.2.13'
+ s.add_development_dependency 'aruba', '~> 0.5'
+ s.add_development_dependency 'cucumber', '~> 1.2'
+ s.authors = ['thoughtbot']
+ s.date = Date.today.strftime('%Y-%m-%d')
- s.summary = "Generate a Rails app using thoughtbot's best practices."
s.description = <<-HERE
Suspenders is a base Rails project that you can upgrade. It is used by
thoughtbot to get a jump start on a working app. Use Suspenders if you're in a
rush to build something amazing; don't use it if you like missing deadlines.
HERE
- s.files = `git ls-files`.split("\n").
- reject { |file| file =~ /^\./ }.
- reject { |file| file =~ /^(rdoc|pkg)/ }
- s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
- s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
- s.require_paths = ["lib"]
-
- s.rdoc_options = ["--charset=UTF-8"]
+ s.email = 'support@thoughtbot.com'
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
s.extra_rdoc_files = %w[README.md LICENSE]
-
- s.add_dependency 'rails', '3.2.12'
- s.add_dependency 'bundler', '>= 1.1'
- s.add_dependency 'hub', '~> 1.10.2'
-
- s.add_development_dependency 'cucumber', '~> 1.1.9'
- s.add_development_dependency 'aruba', '~> 0.4.11'
+ s.files = `git ls-files`.split("\n")
+ s.homepage = 'http://github.com/thoughtbot/suspenders'
+ s.name = 'suspenders'
+ s.rdoc_options = ['--charset=UTF-8']
+ s.require_paths = ['lib']
+ s.summary = "Generate a Rails app using thoughtbot's best practices."
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
+ s.version = Suspenders::VERSION
end
View
2  templates/Gemfile_clean
@@ -11,7 +11,7 @@ gem 'rails', '>= 3.2.11'
gem 'recipient_interceptor'
gem 'simple_form'
gem 'strong_parameters'
-gem 'thin'
+gem 'unicorn'
group :assets do
gem 'coffee-rails'
View
2  templates/Procfile
@@ -1 +1 @@
-web: bundle exec rails server thin -p $PORT -e $RACK_ENV
+web: bundle exec unicorn -p $PORT -c ./config/unicorn.rb
View
26 templates/unicorn.rb
@@ -0,0 +1,26 @@
+# https://devcenter.heroku.com/articles/rails-unicorn
+
+worker_processes (ENV['WEB_CONCURRENCY'] || 3).to_i
+timeout (ENV['WEB_TIMEOUT'] || 5).to_i
+preload_app true
+
+before_fork do |server, worker|
+ Signal.trap 'TERM' do
+ puts 'Unicorn master intercepting TERM and sending myself QUIT instead'
@harlow
harlow added a note

Is there a reason for puts over Rails.logger.info? I'm not sure if it makes any difference in the console output.

@mike-burns Admin

Do we have access to Rails in here?

@croaky Admin
croaky added a note
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ Process.kill 'QUIT', Process.pid
+ end
+
+ if defined? ActiveRecord::Base
+ ActiveRecord::Base.connection.disconnect!
+ end
+end
+
+after_fork do |server, worker|
+ Signal.trap 'TERM' do
+ puts 'Unicorn worker intercepting TERM and doing nothing. Wait for master to sent QUIT'
+ end
+
+ if defined? ActiveRecord::Base
+ ActiveRecord::Base.establish_connection
+ end
+end
Something went wrong with that request. Please try again.