feedPlanet RubyOnRails

01 Jan 1970 12:00am GMT

25 Feb 2009

feedPlanet RubyOnRails

Ben: Announcing the Helpdesk Rails Kit

A new Rails Kit is now available: The Helpdesk Rails Kit makes it easy for you to add a support center to your existing Rails app, or even to just run a standalone support site along-side your existing Rails app. You get an admin interface for you and other people on your team [...]

25 Feb 2009 3:15pm GMT

: Passion Pit - Sleepyhead (Bo Flex Remix ft. Giantess). Best...



Passion Pit - Sleepyhead (Bo Flex Remix ft. Giantess). Best remix ever.

25 Feb 2009 12:40pm GMT

Jesse Andrews: Graceful Caching with Varnish

Your website is being hammered because Hammer told his 120,000 followers on twitter to check your site. The content is highly cachable and no one would notice if you sent the same data for up to a minute. To make matters worse the request takes 5 seconds to complete. Hammer is threatening to send his posse after you. What do you do?

To simulate this theoretical event we will use a small sintra app and apache bench.

require 'rubygems'
require 'sinatra'

get '/work' do
  sleep 5
  body "result: #{rand}"
end

Requests take 5 seconds to process, in addition to any time spent waiting in the request queue. Our single app server processes requests one at a time, so if requests are more often than every 5 seconds, requests will certainly be delayed.

We could add more app servers to improve throughput, but each request would still take 5 seconds, and our concurrency is limited by the number of app servers. Most requests are doing work that isn't needed since the results computed by the last request are good enough.

Getting Varnish

Varnish is a state-of-the-art, high-performance HTTP accelerator, meaning it is built for caching. While your framework might have internal support for memcache or file system based caching, it is worth exploring how Varnish could integrate with your systems. Varnish is a layer between your HTTP frontend server (nginx) and your app servers. Besides caching it constantly checks the health of backend services, and only sends requests to those which are live.

Varnish is currently 2.0.3 at the time of writing. To install on linux:

wget http://downloads.sourceforge.net/varnish/varnish-2.0.3.tar.gz?use_mirror=internap
tar -zxvf varnish-2.0.3.tar.gz
cd varnish-2.0.3
./configure
make
sudo make install

Connecting Varnish to Sinatra

Sinatra is listening on port 4567. We start our sinatra.vcl by specifying where to find sinatra.

backend sinatra {
  .host = "127.0.0.1";
  .port = "4567";
}

We can now start varnishd and verify that http://localhost:1234/work works.

sudo varnishd -f /path/to/sinatra.vcl -a 0.0.0.0:1234

The cache time to live is set in vcl_fetch. We add a ttl of 1 minute to our vcl and restart varnishd.

sub vcl_fetch {
  set obj.ttl = 1m;
}

At this point running ab -c 100 -n 1000 takes 5 seconds to complete. Note that Varnish only sent the first request to the app server, and queued the others. When the request finished the queued requests were served from cache.

Troubleshooting tip: Now comes the frustrating part. Nothing seems to be cached when you test this in your browser. Every request takes 5 seconds. Luckily varnishlog shows your browser is sending a cookie. You remember cookies are per domain (not per domain:port), so cookies set when working on localhost earlier are still being sent. By default Varnish doesn't serve cached results to sessions with cookies. This is useful to serve cached pages to anonymous visitors while logged in users get custom versions of the page. Varnish is quite customizable, but for now we will instruct varnish to ignore cookies in our vcl_recv.

sub vcl_recv {
  unset req.http.cookie;
}

Now we have a painful first request, then all further requests for a minute are speed (my $300 laptop gets over 6,000 req/s with 1000 concurrent requests). After the cache expires, we start over with requests being queued while a single request hits the backend. So every minute all requests are blocked for 5 seconds while the backend works.

Being graceful: release the queue

Varnish optionally allows serving the dirty page during a grace period. Varnish sends a single request is sent to the backend and serves all other requests from the dirty cache.

To make sure we never send pages older than 1 minute, we will reduce the TTL to 45 seconds and add a 15 second grace by updating vcl_fetch/vcl_recv.

backend sinatra {
  .host = "127.0.0.1";
  .port = "4567";
}

sub vcl_recv {
  unset req.http.cookie;
  set req.grace = 15s;
}

sub vcl_fetch {
  set obj.ttl = 45s;
  set obj.grace = 15s;
}

Now a single user will hit a 5 second request every minute but thousands of other requests will benefit from that user's generosity.

More caching

You should be caching the page on the client as well. Adding even a small Cache-Control for the browser will improve visitors experiences.

Wikia/Wikipedia, FunnyOrDie, and others have reported impressive results with Varnish. Varnish is very extensible and supports ESI (server side include). Varnish has great tools (varnishtop, varnishdist, varnishlog) to see how your systems are behaving. Varnish takes headers very seriously, specially regarding cache-control, cookies.

25 Feb 2009 9:30am GMT

Jamis: Net::SSH, Capistrano, and Saying Goodbye

It is with mixed emotions that I announce two things this evening.

First, I'm announcing the final release of both Net::SSH (2.0.11) and Capistrano (2.5.5). Both are minor changes: Net::SSH 2.0.11 adds support for a :key_data option, so you can supply raw PEM-formatted key data. Capistrano 2.5.5 enhances the role() method so you can now declare empty roles. Either way, not much to get excited about, but the changes were pending and deserved releasing.

Secondly: I'm ceasing development on SQLite/Ruby, SQLite3/Ruby, Net::SSH (and related libs, Net::SFTP, Net::SCP, etc.) and Capistrano. I will no longer be accepting patches, bug reports, support requests, feature requests, or general emails related to any of these projects. For Capistrano, I will continue to follow the mailing list, and might appear in the #capistrano irc channel from time to time, but I am no longer the maintainer of these projects. I will continue to host the capify.org site and wiki for as long as they are of use to people.

This was a very hard decision, and one that has taken me months to come to grips with. I cannot express how much I appreciate the huge support from everyone that has found value in Capistrano, in particular. Your kind words and encouragement have meant a lot to me. But I'm burning out, and I have to drop these before things get worse. Maybe after some period of time I'll come back to them-I don't know. But I'm not planning on it.

So where do these projects go from here? That's entirely up to the community. If you have a neat idea for any of these, please feel free to fork the project on GitHub (see my profile page for the links to the individual projects) and release updates on your own schedule. If no one steps forward, that's fine-I'm not asking for volunteers. But if someone feels passionately that any of these are not "finished", and has ideas for how they could be further improved, I will not stand in the way.

However, please know that I am not available for questions about the code, or for advice on how to implement changes. I'm trying to cut as cleanly as I can. Any emails I get asking about the code will likely be ignored. I'm not trying to be rude; I'm just setting expectations.

I won't disappear, though. These libraries were just becoming millstones around my neck; without their weight dragging me down, I look forward to being able to experiment and play with new projects and new ideas. We'll see what the future holds!

So, thanks all for a fantastic couple of years.

25 Feb 2009 4:38am GMT

24 Feb 2009

feedPlanet RubyOnRails

: Camino seems to do a lot better.



Camino seems to do a lot better.

24 Feb 2009 11:28pm GMT

Jesse Andrews: FlashLiteBox - a tiny full screen flash lightbox

Flash was given a full screen capabilities a couple years ago, around the same time JavaScript based lightboxes were becoming popular. While I prefer standards based solutions, I don't know of any other way to implement full screen. My goal was to create a really small flash-based lightbox that uses all your pixels to show you images. While not a finished project, I think this 647 byte swf shows potential.

Overview & demo

The basic idea to overlay thumbnails with a flash movie with arrows indicating you can make the image bigger. If you click the arrows, you switch to full screen and the large version of the image is shown.

You can see a demo at the project page. Sorry about that but flash embeds don't survive rss/email so I'm not even going to attempt it.

Code Walkthrough

FlashLiteBox is built using two open source projects. swfmill takes an xml file of resources and compiles the final swf. MTASC compiles the ActionScript 2 code into a swf for swfmill.

Arrows.as

class Arrows extends MovieClip {
  function onLoad() {
    Stage.align = "TL";
    Stage.scaleMode = "noScale";
    Stage.addListener({
      onFullScreen: function(full) {
        if (!full) _root.image.removeMovieClip();
      }
    });
  }

  function onRelease() {
    if (Stage['displayState'] != "fullScreen") {
      Stage['displayState'] = "fullScreen";
      _root.createEmptyMovieClip("image", getNextHighestDepth());
      _root.image.loadMovie(_root.img_src);
    }
  }
}

onLoad runs when the movie starts. We setup flash not to scale our image and to align the images to the top left. A listener is added which checks for a change to full screen mode. This listener allows removal of the image when escape is hit, leaving the flash movie in its original state.

onRelease runs when the mouse button is released. If the movie isn't already full screen we: enter fullscreen mode, then add a new "movie clip" into we load the image. The URL of the image is provided as a flashvar and accessible via _root.img_src.

This ActionScript is complied into classes.swf.

flashlitebox.xml

<movie>
  <clip import="classes.swf"/>
  <background color="Black"/>
  <frame>
    <library>
      <clip class="Arrows" id="arrows" import="src/maximize.png"/>
    </library>
    <place id="arrows"/>
  </frame>
</movie>

In the final flash movie we load several clips. First we import classes.swf, which is a compiled version of the code above. Then src/maximize.png is added as an image clip (16x16 png). It is given an id, which we use place the movie clip into the main movie frame. The class attribute attaches the actionscript classes we imported from classes.swf.

Further ideas & improvements

The code is open source (BSD) and on github. This is a crude prototype and I am not flash developer, so don't flame me!

I'd love to see someone take this and create a movie usable by people creating galleries. Or perhaps someone should create a greasemonkey script to add it to existing galleries.

24 Feb 2009 9:01am GMT

23 Feb 2009

feedPlanet RubyOnRails

Ben: Another look at integrating Scribd with your Rails application

Almost a year ago I wrote about integrating Scribd into your Rails application, and since then that feature has been working well in my applicant tracking system. Today, though, I got a request for information on how I display the documents that I send to Scribd, so I thought I'd share that, too. In the [...]

23 Feb 2009 9:59pm GMT

ryan: What's New in Edge Rails: Batched Find


This feature is scheduled for: Rails v2.3


ActiveRecord got a little batch-help today with the addition of ActiveRecord::Base#each and ActiveRecord::Base#find_in_batches. The former lets you iterate over all the records in cursor-like fashion (only retrieving a set number of records at a time to avoid cramming too much into memory):

1
2
3

Article.each { |a| ... } # => iterate over all articles, in chunks of 1000 (the default)
Article.each(:conditions => { :published => true }, :batch_size => 100 ) { |a| ... }
  # iterate over published articles in chunks of 100

You're not exposed to any of the chunking logic - all you need to do is iterate over each record and just trust that they're only being retrieved in manageable groups.

find_in_batches performs a similar function, except that it hands back each chunk array directly instead of just a stream of individual records:

1
2
3
4

Article.find_in_batches { |articles| articles.each { |a| ... } }
  # => articles is array of size 1000
Article.find_in_batches(batch_size => 100 ) { |articles| articles.each { |a| ... } }
  # iterate over all articles in chunks of 100

find_in_batches is also kind enough to observe good scoping practices:

1
2
3
4
5
6

class Article < ActiveRecord::Base
  named_scope :published, :conditions => { :published => true }
end

Article.published.find_in_batches(:batch_size => 100 ) { |articles| ... }
  # iterate over published articles in chunks of 100

One quick caveat exists: you can't specify :order or :limit in the options to each or find_in_batches as those values are used in the internal looping logic.

Batched finds are best used when you have a potentially large dataset and need to iterate through all rows. If done using a normal find the full result-set will be loaded into memory and could cause problems. With batched finds you can be sure that only 1000 * (each result-object size) will be loaded into memory.

tags: ruby, rubyonrails

23 Feb 2009 9:30pm GMT

James Stewart: Hacking wordpress to support per-post banner images

I seem to be spending a lot of time with wordpress at the moment. It's become so ubiquitous that it often makes far more sense to set it up and integrate with an existing app than to set up some other blogging system and re-train users. As a result I've been writing a few wordpress [...]

23 Feb 2009 1:34pm GMT

Jesse Andrews: BlogBiking: Making RSS Healthy

How I Roll Exercise is boring. You know you should, but you don't make time. Motivated by warbiking (playing warcraft while exercise biking), but not having time for it, I applied the concept to my existing time sinks. Once passive tasks (blogs, TED talks, pdfs) become active tasks while biking.

Previously I would attempt to multi-task. Sitting at my desk I would attempt stupid things like watching a video while skimming articles. While effective at pruning unnecessary emails/posts, I rarely remembered the video afterwards. Now I read articles or watch videos while biking. Because I'm biking I'm less fidgety and can concentrate on one thing since I know at the very least I'm staying healthy.

A new Schwinn recumbent bike is $400, but you can find them used on craigslist. I bought mine for $150 into which my X60 fits perfectly.

Don't break the chain

Seinfeld Calendar It is easy to lose motivation and stop. After a few weeks of blogbiking in 2007 I stopped for a month. Until this year I've only biked intermittently as I have to consciously remember to move to the bike.

Jerry Seinfeld knew the importance of showing up every day. By working on your craft every day, you keep moving forward, even if the step is a small one. By recording daily progress, you will be motivated to not skip a day (breaking the chain). Knowing I've biked at least 5 miles for 32 continuous days pushes me to not skip a day.

At first I overcomplicated the process. I tried calendar about nothing and other online progress trackers. My Christmas calendar and a sharpie was all I needed. Each day I write my current streak length on today's date.

I know more about COBOL

Somedays I have to bike at midnight when I get home. I need to find an alternative exercise I can do while traveling or during a break at work.

As an exercise n00b, I'm unsure how to optimize for tone/health. Currently I bike at slow pace (10mph) with a high resistance, but perhaps programs that vary resistance (strength interval, ride in park, pyramids, ...) would be better?

23 Feb 2009 2:11am GMT

22 Feb 2009

feedPlanet RubyOnRails

Jesse Andrews: Sending Email Alerts with BackgroundJob

Web requests should be lazy, putting off any work they can. When a user updates a script on userscripts.org I send an email alert sent to its fans. But the uploader shouldn't have to wait while hundreds of people are emailed. There are many systems that allow for asynchronous tasks in rails. Here is what worked best for me.

No spam please

Since I don't want to be a spammer, I need to allow a method for users to opt-in to receiving updates of their favorite scripts. I've added a column to our user table to store whether a user wants emailed.

add_column :users, :email_favorite_activity, :boolean

UI changes are made to the settings page allowing them to subscribe to alerts. Next I make sure emails include messaging about why they were sent and how to stop getting them.

A small job

Next I create a directory called jobs in the userscripts project directory. I add a trival ruby script emails all the fans that opted into email of the passed in script. This code runs within the rails environment allowing the use of ActiveRecord, ActionMailer and all their friends.

script_id = ARGV[0]
script = Script.find(script_id)
users = script.fans.select { |u| u.email_favorite_activity }
puts "Emailing #{users.count} fans"
users.each do |fan|
  Notification.deliver_new_script_version(script, fan)
end

The job can be ran manually using rails' built-in script/runner.

script/runner ./jobs/email_script_fans.rb 42

In development mode the emails won't be sent, but I can see I'm ready to integrate the task.

Why starling backgroundrb bj?

Starling is a queue system which speaks the memcache protocol. It was designed by Blaine Cook to allow Twitter to route with a massive of tweet. Starling requires a server daemon, and workers that are constantly asking for queued items. My email load is a fraction of a fraction of twitter's load, so the increased complexity isn't worth it.

BackgroundRB was created by Ezra Zygmuntowicz (of EngineYard). BackgroundRB requires a daemon as well, and if that daemon isn't running adding a task fails. After spending a few hours I could see the potential but the complexity was overkill for sending a few emails.

BackgroundJob (BJ) was a perfect fit. Jobs are stored in your database, so you don't have to worry about deploying and monitoring another daemon. I was initially concerned that tasks are not rails specific. Task are ran as regular processes, with the stdout/stderr being stored in columns after completion. To run rails code you use script/runner. Each job requires a new script/runner, so the overhead of loading rails for each task can become painful at volume.

BJ was received support from EngineYard/Ezra and the community has taken over BackgroundRB. There are other projects as well, but none of them seemed to:

Installation

Start by install BackgroundJob as a plugin.

script/plugin install http://codeforpeople.rubyforge.org/svn/rails/plugins/bj
script/bj setup

Unfortunately BackgroundJob has a couple issues with the timezone improvements in rails 2.1. The fix is to change all instances of Time.now to Time.now.utc in two of the files.

vendor/plugins/bj/lib/bj/runner.rb
vendor/plugins/bj/lib/bj/table.rb

Then I trigger a job to be added when a new version of a script is uploaded.

class Version < ActiveRecord::Base
  after_create :email_fans

  def email_fans
    Bj.submit "./script/runner ./jobs/email_script_fans.rb #{self.script_id}"
  end
end

Server Setup

BackgroundJob upon receiving a new job will launch a worker if there isn't one running already, so after a cap deploy, emails will be sent.

Harder, Better, Faster, Stronger

Harder: You need to setup bounce detection. Sending an emails out without checking for bounces is a recipe for being blocked as your volume increases. If an email bounces twice in a month disable updates and ask them to update their email the next time they visit.

Better: Add the ability for users to select HTML or text based emails.

Faster: You can speed up the process by making the email body and subject is the same for all fans, then you should use BCC to send a single email to all recipients at once.

Stronger: Generate a daily email to yourself that tells you how many emails were sent. Have a cron entry generates this daily job.

22 Feb 2009 8:53am GMT

21 Feb 2009

feedPlanet RubyOnRails

john: Shoulda Looked At It Sooner

In which I explain what I like about shoulda after using it for a few hours.

21 Feb 2009 10:37pm GMT

Jesse Andrews: Log Rotation for Phusion Passenger

Deploying a Rails site with Passenger removed a lot of complexity. No more monit/god to monitor and restart mongrels. No more HAProxy/Pound/Nginx hacks to load balance between mongrels. But don't forget that while Apache will be setup to rotate its logs, your rails logs will consume your entire hard drive if you don't add a logrotate for them.

Add /etc/logrotate.d/passenger

A logrotate configuration file specifies the location of the logs with rules about how to modify them. Your capistrano deploy.rb will specify the base path that contains shared/log directory and current/tmp which our rotate script needs.

set :deploy_to, "/home/deploy/app"

Here is what I've found to work for me.

/home/deploy/app/shared/log/*.log {
  daily
  missingok
  rotate 30
  compress
  delaycompress
  sharedscripts
  postrotate
    touch /home/deploy/app/current/tmp/restart.txt
  endscript
}

Interpretive 'man logrotate'

Testing and hourly rotation

Assuming you've been running your site for a while you will have some existing logs you want to test your new setup with. Rather than staying up all night, you can manually run logrotate using:

logrotate -f /etc/logrotate.d/passenger

Then you should have .1 files for each log in the log directory. Passenger should be restarting, and a new production.log should start growing with each new request.

Logrotate is smart enough to only run once daily even if you try to run it manually. There is no hourly option for logrotate, but if you remove daily and add size 1 you trick logrotate into rotating every time you call it. Cron will still only call it once daily, but you can add your own hourly call. Before newrelic I would rotate my logs hourly and analyze them with production log analyzer in postrotate.

Void where prohibited

I hope this helps make sure your server doesn't crash due to run away log files. Your setup might be different but logrotate is pretty easy to tweak if you know where to look.

21 Feb 2009 9:26am GMT

20 Feb 2009

feedPlanet RubyOnRails

brian: Caveat Lector

I really did double-check this time and I won't be making any wild claims here. Sorry to disappoint.

We're going to be running Antonio's Ruby Benchmark Suite daily to track our progress on performance in Rubinius. The current RBS is a bit of a beast so I imported the files into the Rubinius repository and did some refactoring. You can read the details and up-vote that if you'd like to see this merged back.

Now, for some baseline RBS results. If you want to follow along at home, here's what I did. I generated these by running the rake bench task using the VM option (see the benchmark/utils/README in the Rubinius repository) for Rubinius on the stackfull and master branch and for MRI using the version installed on Debian lenny, 1.8.7p22. The system is a dual Intel® Xeon™ CPU 2.40GHz. Then I ran the rake bench:to_csv task, imported the CSV file into Google Docs, added the comparison columns and colors, and exported to PDF.

Here's what I got. The green is faster, the red is slower. The reported time is the minimum time recorded in five "iterations" of each benchmark per input. The maximum time allowed to run five iterations is 300 seconds, or an average of 60 seconds per iteration.

A few notes about these numbers:

Perhaps the biggest point about the stackfull branch is that we haven't done much optimization at all. Evan's been coding in the basic new interpreter architecture, fixing the GC interaction, adding the native threading. We're fixing breakage now so we can get this merged into the master branch. The JIT is not hooked up. The new GC work is not done. There is no inlining. In other words, there is lots of head room. And that is the key point. You can't just "make it faster". Architecture is crucial. Since RailsConf 2008, we've been working hard to lay the architectural foundations. With those (and the switch away from stackless), we can start focusing on the real dynamic language optimizations.

While the benchmarks tell part of the story, there's another part that is even more interesting IMO. And this is the part that got me so excited I, um, well I just got excited...

The two biggest pieces of Ruby software that we most often run are the Rubinius compiler and the RubySpecs. The RubySpecs are much more "real-world" than these benchmarks. Here are the results of two complete CI runs on master and stackfull. Note that we are not quite running all the basic CI specs on stackfull, but we'll figure in that difference in our calculations below.

First, on master:

  $ bin/mspec ci --gc-stats
  rubinius 0.10.0 (ruby 1.8.6) (f4c5576c4 12/31/2009) [i686-apple-darwin9.6.0]

  Finished in 131.248169 seconds

  1430 files, 6927 examples, 23006 expectations, 0 failures, 0 errors

  Time spent in GC: 51.6s (39.3%)

And then on stackfull:

  $ bin/mspec ci --gc-stats
  rubinius 0.11.0-dev (ruby 1.8.6) (e7b6a2d56 12/31/2009) [i686-apple-darwin9.6.0]

  Finished in 66.357996 seconds

  1349 files, 6298 examples, 21344 expectations, 0 failures, 0 errors

  Time spent in GC: 12.7s (19.1%)

Let's calculate how we do in expectations per second:

  $ irb
  >> master = 23006 / 131.248169
  => 175.286254850534
  >> stackfull = 21344 / 66.357996
  => 321.649255351232
  >> stackfull / master
  => 1.83499416782851

So, compiling and running the specs is about 1.8 times faster on stackfull. This is upside down from the normal results. Normally, we do better on the micro benchmarks and see that invert on "macro" benchmarks. On the RBS benches, stackfull is not 1.8 times faster than master. If I average the "x Master" column, I get 1.39.

There was something else in those spec run numbers I wanted to talk about… oh yeah, GC stats. We have a very simple GC timer stat right now. I'm going to be adding a few more stats. But what we see here is that the overall percentage of time spent in GC drops by half in stackfull. Even so, 19% is too much time to spend in GC. We expect to drop that by half again. Basically, leaning more on structures alloca'd on the C stack reduces a lot of pressure on the GC.

Some would toss out that it's not hard to be faster than MRI. Perhaps. But it is an accomplishment to write a reasonably good VM, garbage collector, compiler, and Ruby standard library without importing anyone else's code. And, lest we forget, that is two VM's in about 27 months of a public project.

Some would also question the sanity of writing a VM and garbage collector when crazy smart people do things like that. Well, crazy smart people write papers that reasonably smart people can read and understand. From the benchmark result above, that is working pretty well.

Here's the point: Don't ever let anyone tell you that something is a bad idea. Make your own decisions. We probably wouldn't have Ruby itself if Matz fretted over whether Larry Wall or Adele Goldberg were smarter than he. My most recent favorites in this space: Factor, Clojure, and yes, tinyrb.

We're working frantically to get the stackfull branch breakages fixed and the branch merged back into master. Feel free to poke around and ask questions.

20 Feb 2009 11:36pm GMT

Jesse Andrews: Deploying Without Losing Style Points

You've done everything Steve Souders said to speed up your website. Perhaps you practice markup haiku, but you definitely combine stylesheets and scripts into single assets. You've made capistrano perform rolling restarts via haproxy so that your visitors always have mongrels waiting for you. Yet for some reason visitors receive an unstyled version of your site every time you deploy!

GetSatisfaction.com without its style
rails based Get Satisfaction missing its skin


You cannot serve what doesn't exist

For simplicity, assume we combine stylesheets using stylesheet_link_tag :all, :cache => true.

As capistrano finishing deploying current is re-symlinked to the new deploy. Before our mongrels finish restarting a request for all.css arrives. Since no mongrels running in the new current has rendered a template with our stylesheet_link_tag, the combined all.css won't exist. Your webserver returns a 404 and your visitor gets to see your dirty non-semantic html.

The file will not exist until a mongrel that has been restarted renders the pages. The time required to complete a rolling deploy and volume of requests can affect how long this takes. I've seen up to 30 seconds before enough mongrels have been restarted to generate the static content.

Doing it by hand

You need generate your combined assets progmatically on each deploy. For cliKball, we have a rake task that loads enough of an environment to run stylesheet_link_tag, which in turn generates our static files.

task :build_cache => :environment do
  include ActionView::Helpers::TagHelper
  include ActionView::Helpers::UrlHelper
  include ActionView::Helpers::AssetTagHelper
  stylesheet_link_tag :all, :cache => true
end

Then we added a hook to deploy:update_code to capistrano, so that our rake task is run before the symlink is modified.

after "deploy:update_code", "deploy:prepare_static_cache"

namespace :deploy do
  task :prepare_static_cache do
    # SASS -> CSS -> all.css; all.js
    run "cd #{release_path}; rake RAILS_ENV=#{rails_env} build_cache"
  end
end

But I use Passenger?

This was a very simple example but the issues and solutions are similar for more complicated environments. With Passenger, if you have a large number of processes for your site, your visitors could get a 404 while waiting for the processes to be reloaded. On userscripts.org it takes about 20 seconds to restart, leaving an undefined window, during which time 100 requests queue up. Knowing how visitors will be affected during these times is required if you want to always be shipping.

Since you are using 10 year expiry times on timestamp based urls, visitors might have a painful experience until the next time you deploy. So if you combine, you must take precautions when you deploy.

20 Feb 2009 6:30am GMT