Skip navigation

Active Product Development

3 Posts tagged with the rails tag

https://github.com/images/error/angry_unicorn.png

The general internet discovered Unicorn after a blog post over at GitHub. I've been wanting to use it for a while now. Early this week we switched over Trainer 2 to this awesome web server.

 

The idea behind Unicorn is that instead of having a separate load balancer with all kinds of fancy rules to determine which workers are busy and which are available to do some work, you let the tried and true process management built into your OS do that work for you. Unicorn consists of a master process that spawns workers. The master monitors the workers and kills and restarts any that are behaving badly. When a request comes in the master hands it off to the operating system to determine which worker process has the resources available to work on it. The stack looks like this:

 

https://d3nwyuy0nl342s.cloudfront.net/img/a91702456cf39acf5548524593fb19c6d1a84c42/687474703a2f2f696d672e736b697463682e636f6d2f32303039313030392d6e686b787072726e7463346b39753178346b62653134783631742e706e67

You stil want to keep your web server (nginx in our case, or Apache) serving static content. You just let the server know where to pass all the other requests to. In nginx this looks something like:

 

upstream trainer2 {
  server unix:/tmp/unicorn.sock fail_timeout=0;
}
server {
  listen 80;
  server_name trainer.active.com;
  root /var/www/current/public;

  location / {
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_max_temp_file_size 0;

    # serve static content with a far-future expires header
    location ~ ^/(images|stylesheets|javascripts)/ {
      expires 10y;
    }

    # if the file exists (static stuff) serve it right away
    if (-f $request_filename) {
      break;
    }

    # otherwise pass it on to unicorn
    if (!-f $request_filename) {
      proxy_pass http://trainer2;
      break;
    }
  }
}

 

 

That's it for nginx. Now to start up Unicorn you need a config file (ours is in /config/unicorn.rb):

 

# Sample verbose configuration file for Unicorn (not Rack)
#
# This configuration file documents many features of Unicorn
# that may not be needed for some applications. See
# http://unicorn.bogomips.org/examples/unicorn.conf.minimal.rb
# for a much simpler configuration file.
#
# See http://unicorn.bogomips.org/Unicorn/Configurator.html for complete
# documentation.

root = '/var/www/current'

worker_processes(ENV['RACK_ENV'] == 'production' ? 8 : 2)
working_directory root # available in 0.94.0+
listen "/tmp/unicorn.sock"
timeout 120

pid "#{root}/tmp/pids/unicorn.pid"

stderr_path "#{root}/log/unicorn.stderr.log"
stdout_path "#{root}/log/unicorn.stdout.log"

preload_app true
GC.respond_to?(:copy_on_write_friendly=) and GC.copy_on_write_friendly = true

before_fork do |server, worker|
  defined?(ActiveRecord::Base) and ActiveRecord::Base.connection.disconnect!

  old_pid = "#{root}/tmp/pids/unicorn.pid.oldbin"
  if File.exists?(old_pid) && server.pid != old_pid
    begin
      Process.kill("QUIT", File.read(old_pid).to_i)
    rescue Errno::ENOENT, Errno::ESRCH
      # someone else did our job for us
    end
  end
end

after_fork do |server, worker|
  defined?(ActiveRecord::Base) and ActiveRecord::Base.establish_connection
end

 

 

Now we just start up Unicorn like so (from our application's root directory):

 

unicorn -E production -c config/unicorn.rb -D

 

And that's it! You should be able to hit your server and have Unicorn respond. The true magic starts when you want to do a deploy: you don't have to have any downtime because Unicorn will start up a new master while the old one continues to serve clients. Once the new master is up and running, the first worker it spawns will turn off the old master. Users never saw a delay and you were able to upgrade your app transparently. Using this technique you can upgrade Unicorn itself, or even Ruby with no downtime! To trigger this restart just send the USR2 signal to Unicorn. First, run a ps aux | grep unicorn to get the process list and send the signal to the existing master process:

 

ubuntu@qa:~$ ps aux | grep unicorn

USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
ubuntu   23511  0.0  4.0  80216 71040 ?        Sl   May04   0:15 unicorn master -E qa -c config/unicorn.rb -D                                    
ubuntu   23641  0.0  5.0  99108 88576 ?        Sl   May04   0:51 unicorn worker[0] -E qa -c config/unicorn.rb -D                                 
ubuntu   23644  0.0  5.0  98004 87632 ?        Sl   May04   1:14 unicorn worker[1] -E qa -c config/unicorn.rb -D

ubuntu@qa:~$ kill -USR2 23511

 

After a few seconds (however long it takes for your Rails app to start) you'll begin serving your new code. Done!

1,605 Views 0 Comments Permalink Tags: trainer, ruby, rails, unicorn

ActiveRecord is great but it can be a little scary to think about using it without Rails. Here's a quick example of using it with Sinatra.

 

The first thing we'll want to do is get the ActiveRecord gem by itself (throughout this tutorial I'm assuming you're using Bundler to manage your gems):

 

gem 'activerecord', :require => 'active_record'

 

Next up, in the configure block of our Sinatra app, we'll set up the AR connection to our database:

 

ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => 'db/development.db')

 

Of course, if you want to stay flexible, you can move your database config into a YAML file (laid out the same as the Rails standard config/database.yml file) and pull the connection details from that:

 

db_config = YAML::load(File.open(File.join(File.dirname(__FILE__),'config','database.yml')))[Sinatra::Application.environment]
ActiveRecord::Base.establish_connection(db_config)

Now that AR is ready to go we can include our models. I like to move my models into separate files, similar to Rails's app/models, so I tell Sinatra to include every file in that directory:

 

Dir.glob('./app/models/*').each { |r| require r }

 

Believe it or not, that's really all you need to do to start using AR in your Sinatra application! The only missing link is database migrations. AR comes with a huge Rake file full of tasks like db:migrate, but unfortunately those rely on being inside the shell of Rails (they look for environment variables like RAILS_ENV and the base Rails object all over the place). Here's a very simple piece of code that will enable the standard db:migrate task:

 

require 'bundler'
Bundler.require


desc "Migrate the database through scripts in db/migrate."
task :migrate do
  ActiveRecord::Base.establish_connection(YAML.load(File.read(File.join('config','database.yml')))[ENV['ENV'] ? ENV['ENV'] : 'development'])
  ActiveRecord::Migrator.migrate("db/migrate/")
end

 

This assumes that your migration files are in db/migrate. When you want to migrate in an environment other than development just set the ENV variable:

 

rake db:migrate ENV=production

 

That's it! There are a couple of different database ORM libraries out there besides ActiveRecord (like Sequel and Datamapper) but AR is the most popular today and it's easy to use it in all of your web apps, whether they're Rails or not.

9,486 Views 0 Comments Permalink Tags: ruby, sinatra, rails, database, activerecord

Ruby on Rails comes with a bunch of great rake tasks to help you work with your application. One of them is rake stats which counts the lines of code in your controllers, models and tests. It's a pretty neat utility that we use all the time to make the Java developers jealous.

 

I've been working on a little Sinatra application and wanted to use the same functionality. The rake stats command used the CodeStatistics class from one of the core Rails packages called railties (that's "rail-ties" as in the wooden slats that hold up train tracks, not "rail-tees" like t-shirts about trains). Now, the good Ruby developer in me says to simply require 'railties/code_statistics' from in my Sinatra application. However, I also realize that my Sinatra application has nothing to do with Rails and never will. So I did the unthinkable: copy-paste. In this case I don't think it's that bad: code_statistics probably hasn't changed much since Rails 1.0 and it's not something that I care about keeping in sync with the master development branch of Rails. Don't sweat it.

 

I put code_statistics.rb in a new directory in my Sinatra app called vendor which is a Rails convention for code from third-parties. Normally I would just put this code into lib, but in this case rake stats includes the lines of code in any files in lib, and I didn't want to throw off my numbers. In my Rakefile I added a new task (also copied from the Rails default Rakefile with a couple modifications):


desc "Report code statistics" task :stats do   require './vendor/code_statistics'     STATS_DIRECTORIES = [     %w(Controllers        app/controllers),     %w(Helpers            app/helpers),     %w(Models             app/models),     %w(Libraries          lib/),     %w(Migrations         db/migrations),     %w(Views              app/views)   ].collect { |name, dir| [ name, "./#{dir}" ] }.select { |name, dir| File.directory?(dir) }   CodeStatistics.new(*STATS_DIRECTORIES).to_s end

 

The modifications in this case are the directories that should be scanned for code lines. By default Rails doesn't include your views as counting towards your totals, but I wanted them to. I also have lots of migrations and would like to know how those contribute as well.  If you have any directories in addition to these just add them to the STATS_DIRECTORIES constant. I also wanted to count the number of comments in my code. I'm a big proponent of commenting and I'd like to know what ratio of my code is used to tell my future self how everything works. You can get a copy of my code_statistics.rb file (with comment-counting mods) here: https://gist.github.com/823219

 

And that's it! Now just rake stats from the root of your Sinatra app and you'll see something like this:

 

+----------------------+-------+-------+----------+---------+---------+-----+-------+ | Name                 | Lines |   LOC | Comments | Classes | Methods | M/C | LOC/M | +----------------------+-------+-------+----------+---------+---------+-----+-------+ | Controllers          |    65 |    50 |        8 |       0 |       0 |   0 |     0 | | Helpers              |    41 |    32 |        0 |       0 |       7 |   0 |     2 | | Models               |    12 |    10 |        0 |       2 |       1 |   0 |     8 | | Libraries            |     8 |     6 |        1 |       2 |       1 |   0 |     4 | | Migrations           |    78 |    71 |        4 |       0 |       0 |   0 |     0 | | Views                |    45 |    39 |        0 |       0 |       0 |   0 |     0 | +----------------------+-------+-------+----------+---------+---------+-----+-------+ | Total                |   249 |   208 |       13 |       4 |       9 |   2 |    21 | +----------------------+-------+-------+----------+---------+---------+-----+-------+   Code LOC: 208     Test LOC: 0     Code to Test Ratio: 1:0.0

 

1,567 Views 0 Comments Permalink Tags: ruby, sinatra, rails, rake