Wednesday, June 20, 2007

Deploying Rails applications to multiple environments

I love capistrano, however I had some trouble finding solid doc on how to deploy your app to multiple environments (UAT, staging, etc.) So here is a quick and dirty of the solution I can up with.

cap deploy

The old standby, this will configure our deployment based on deploy.rb. We want to conditionally configure our deployment so we need to pass a parameter to this comment, this is done with -S

cap deploy -S stage=uat

This sets a local variable 'stage' to 'uat' in our deploy.rb

Here is our conditional code from deploy.rb

if stage == 'production'
set :application, "foo"
set :repository, "http://svn.agiledog.com/svn/agiledog/foo/trunk"

# Without this $RAILS_ENV on the destination server will be used to determine the server environment
set :rails_env, :staging

role :web, "app.agiledog.com"
role :app, "app.agiledog.com"
role :db, "app.agiledog.com", :primary => true
elsif stage == 'uat'
# Different application names are used to allow different stages to be deployed on the same server if required
set :application, "uat-foo"
set :repository, "http://svn.agiledog.com/svn/agiledog/foo/branches/RB-4.0"
set :rails_env, :uat

role :web, "uat.agiledog.com"
role :app, "uat.agiledog.com"
role :db, "uat.agiledog.com", :primary => true
else # assume app == 'staging'
set :application, "staging-foo"
set :repository, "http://svn.agiledog.com/svn/agiledog/foo/trunk"
set :rails_env, :staging

role :web, "stage.agiledog.com"
role :app, "stage.agiledog.com"
role :db, "stage.agiledog.com", :primary => true
end

We have added two new environments, staging and uat. Both of these will need to be added to the database.yml file. We will also need to create staging.rb and uat.rb to the config/environments folder to declare any specific environment settings.

One gotcha with this solution is that you will always need to set the stage variable anytime deploy.rb is read. That is to say, even though we assume the staging enironment, the following command will fail.

cap deploy

Instead you need to use:

cap deploy -S stage=staging

Tuesday, June 19, 2007

The Butt Kicker

Embarrasing moments at work.

Day: -5
Mood: Very good
Chris sends link to daily woot. As a result of good mood and caffine I purchase woot as a graduation gift for Jimmy. To ensure that I get woot as soon as possible I have it shipped to my office in SF so as not to miss the fed-ex guy.

Day: -4
Mood: Remorseful
Reality that I have just shipped a silly item from woot to my workplace sets in.

Day: -3
Mood: Concerned
Woot has still not shipped, not sure if it is going to make it in time.

Day: -2
Mood: Ambivilant
No woot. It's not going to make it. Oh well.

Day: 0
Mood: Elated
Graduation party rocks, no woot, but horseshoes and smashing a truck with a sledgehammer focuses my mind elsewhere.

Day: +4
Mood: Concerned
Woot has arrived at work. Working from home so cannot retrieve it until next week.

Day +5
Woot sits on desk at work.

Day +9
Mood: Embarassed
Come in to work to find massive box on desk (woot). The clearly labeled package has been aligned like an art piece so that all may revel in it's existance. It seems to saw, 'Behold, this idiot bought a Butt Kicker'.
http://www.thebuttkicker.com/ButtKicker%20Gamer_home.html



Day +433
Mood: Victorious
After more then a month in Twitters office, two weeks in the back of my car, a month and a half in my living room, and the remainder in my garage, the butt kicker was finally delivered to my brother as a college graduation present. After an epic odyssey I am proud to report that the butt kicker is kicking butts. My brother reports that on a medium setting the device delivers enough force to throw him from his office chair. A project is in the works to secretly mount the device inside his couch, allowing the delivery of a brown noise class audible to unsuspecting guests. Kick on butt kicker, kick on.

Thursday, June 14, 2007

Rails threads vs. the Mongrel pack

Skip to the bottom if you have an aversion to reading.

I was very dissapointed to find out that Rails is not thread safe. Well, that's not totally true but I'll come back to that. What this means is that we cannot put every request to our rails application in it's own thread. Every request needs to line up and wait for rails to proccess it.

Using your out of the box script/server command this can be problamatic.

Imagine you can only buy the newspaper one page at a time, and you have to buy it from your freiendly neigherhood homeless person. Now this may not be so bad assuming you are the only person trying to read the newspaper.

But now the newspaper is getting popular, and there are 5 other people want to read the paper as well. It's still ok until Mary the town gossip spends 30 seconds buying her paper. Now everyone unfortunate enough to try and get a page during this time will have to wait those 30 seconds.

Mongrel to the rescue, sorta. So now instead of one homeless person we have a pack of homeless people (pack of mongrels). If one is slowed down by the town gossip you have other homeless people waiting to sell you that same page of the paper.

But there is a catch, you and all your fellow newspaper readers are blind (ironic, I know) and thus you can't see what homeless person has the shortest line. Lucky for you the native american girl Apache22 is willing to direct you to what she thinks is the best line. Unfortunatly she is not that great at this (through no fault of her own) and she can only tell when a line is in really bad shape.

As a concerned newspaper distributer what do we do? Round up more mongrels, but they come at a price. Adding more mongrels means more memory you will have to allocate for them. Each one is an entire Rails process. Depending on the app you are looking at 50-200 MB each. Whew, that sounds like I have to do some math, and I hate to think, is there an easier way?

If Rails were thread safe we could spawn a thread (homeless newspaper seller) for every request that comes in. Threads use much less memory then processes so we're not so worried about memory consumption.

But Rails is thread safe! Yes, I think it is, however is your application? And all those nifty plugins you installed? Thread safety is something we have to think about when developing, and we don't want our developers to have to think any more then they already do.

Can't we just kill all the people who are too slow? That's sick, but what we can do is make sure that all of our requests are served in a similar amount of time. It should be pretty obvious that most page requests should be returned in a matter of seconds, but if we are using AJAX to run a fatty report or search, that may not have crossed our minds. After all, that's why we have spinner.gif.

Is there a way to run a long process in the background? Of coarse, http://backgroundrb.rubyforge.org to the rescue. You will have to setup an observer on the client side that keeps asking the server if the job is done yet, but those requests will be small and fast.

So that's all. Let's review:

Threads:
- Applications need to be thread safe
- Small memory footprint

Processes (Mongrels!):
- Slow requests can hold up faster ones
- Larger memory footprint (~50-200MB)
- Architect needs to determine how many mongrels to run

Conclustion:
After reviewing the facts, threads are not as cool as I thought they were.

This is a gotcha more then a defect with rails.

Gotcha:
Rails does not run with threads by default. If you have actions that take a long time to process, you may ruin your performance long before anything else (database, disk I/O) starts to effect you application.

Time to start

It's offical, now that Chris is going to start blogging I must follow suit. Look forward to posts and responses to other people's outlandish statements regarding time traveling and black holes.