Posts tagged with: "performance"
Built For Speed: Using Amazon CloudFront To Serve Assets
This is the second in a series of posts on improving your site’s performance with the help of the YSlow Firefox plugin. In the last Built for Speed post, we took a look at YSlow’s most important factor in page speed – the number of HTTP requests. We demonstrated using the AssetPackager plugin to help reduce both the number of HTTP requests and the size of your CSS and JavaScript files. The source for the Built for Speed application is available on Github.
This week, we’ll learn how to use a Content Delivery Network (CDN) to help users see our static content faster. Granted, this may be overkill for a lot of sites, but I think it’s worth the time to see how it’s done. There are a lot of CDNs out there, but I’ve decided to use Amazon’s CloudFront because it’s relatively cheap and easy to set up (not to mention it integrates seamlessly with S3). Before you get much further, you’ll want to set up an account with S3 and CloudFront.
First, let’s install the Paperclip plugin so we can upload an image to go with our post.
script/plugin install git://github.com/thoughtbot/paperclip.git
Next, we need to add the Paperclip fields to the Post model:
script/generate migration AddPaperclipColumnsToPost
# in the newly-created migration
def self.up
add_column(:posts, :image_file_name, :string)
add_column(:posts, :image_content_type, :string)
add_column(:posts, :image_file_size, :integer)
add_column(:posts, :image_updated_at, :datetime)
end
def self.down
remove_column(:posts, :image_file_name)
remove_column(:posts, :image_content_type)
remove_column(:posts, :image_file_size)
remove_column(:posts, :image_updated_at)
end
We also need to make the Paperclip declaration in the Post model:
class Post < ActiveRecord::Base
has_attached_file :image, :styles => {:large => "500x500", :medium => "250x250", :thumb => "100x100"}
end
And finally we make the updates to the views. First change the new and edit views, adding the file_field for the attachment and making sure the form is set to accept multipart data (see the source on Github if you have questions). Then update your show view to display the image:
<%= image_tag @post.image.url(:large) %>
Now let’s take a look at our application in Firefox. Remember, we’re running it in production mode to see the benefits of the AssetPackager plugin among other things. Create a new post with the image attachment of your choice.
One gotcha – if you are running your application using Passenger, you may see an error something like this when you try to create a Paperclip attachment:
Image /tmp/passenger.621/var/stream.818.0 is not recognized by the 'identify' command.
In order to avoid this, create a file in /config/initializers to tell Paperclip where to find ImageMagick. I installed ImageMagick using MacPorts, so my file looks like:
Paperclip.options[:command_path] = "/opt/local/bin"
Okay, now that we have a new post with an image, browse to the post detail page, open up the YSlow interface, click “Run Test” and take a look at the second grade, “Use a Content Delivery Network (CDN)”.
Ugh, we got a “D”. Okay, let’s see how we can implement Amazon CloudFront to make that grade an “A”.
Let’s start by telling Paperclip to use S3 for our image storage. Go ahead and create a configuration file called amazon_s3.yml. Obviously, you’ll need to replace the values here with your own keys:
# config/amazon_s3.yml
development:
access_key_id: 123...
secret_access_key: 123...
test:
access_key_id: abc...
secret_access_key: abc...
production:
access_key_id: 456...
secret_access_key: 456...
Paperclip depends on the ‘right_aws’ gem for its S3 storage, so make sure you add that to your config.gem list in /config/environment.rb and install it with:
rake gems:install
Next, update the Post model so it will use the new S3 configuration:
class Post < ActiveRecord::Base
has_attached_file :image,
:styles => {:large => "500x500", :medium => "250x250", :thumb => "100x100"},
:storage => 's3',
:s3_credentials => YAML.load_file("#{RAILS_ROOT}/config/amazon_s3.yml")[RAILS_ENV],
:bucket => "built-for-speed",
:path => ":class/:id/:style.:extension"
end
Now restart your application and create a new post with an image. When you get to the post detail page, check out the source and you should see that your image is being served from your S3 bucket. That’s great, but what we really want to do is serve the image from the CloudFront CDN. The easiest way to do this is to install the S3 Firefox Organizer plugin). Once you enter your credentials, you should see your newly-created ‘built-for-speed’ bucket. Right-click on the bucket name and click “Manage Distributions”, then optionally add a comment and click “Create Distribution” (we’ll skip the CNAME option for now).
This will generate a new resource URL for you to use in your application so you can take advantage of CloudFront. Now we have to go back and tell our application to use this resource URL:
# /config/initializers/cloudfront.rb # # Note that your CloudFront resource URL will be different CLOUDFRONT_DISTRIBUTION = "http://d2qd39qqjqb9uw.cloudfront.net"
# in /app/models/post.rb
def cloudfront_url( variant = nil )
image.url(variant).gsub( "http://s3.amazonaws.com/built-for-speed", CLOUDFRONT_DISTRIBUTION )
end
# in /app/views/posts/show.html.erb <%= image_tag @post.cloudfront_url(:large) %>
Restart the application and go back to the post detail page. Inspect the source and you’ll see that your image is now being served from CloudFront.
Okay, I think that’s enough for today. In the next post, I’ll show you how to avoid CloudFront serving stale assets and how to make YSlow recognize that you are now using a CDN. Please leave any questions or comments below.
CREDITS/RESOURCES:
Built For Speed: Using the AssetPackager Plugin
Inspired by the recent launch of code.google.com/speed, I decided to sit down and see how I could apply their guidelines. This is the first in a series of posts on improving front-end performance for your Rails applications.
First of all, we need to create our sample application. Recently, I’ve been using Beet, a gem for generating Ruby projects, but you can create your local version however works for you. Using Beet, the following command tells the Rails generator to use MySQL, remove unused files (public/index.html, etc.) and initialize a Git repository:
beet -g built_for_speed -r="rails/db/mysql, rails/clean_files, rails/git"
Next, let’s create a Post resource:
script/generate scaffold Post title:string body:text
Make sure your databases are created, and run the migrations. Note that for the purposes of this tutorial, I’m running the application in production mode (to better see the speed benefits), using Passenger on my MacBook Pro. By the way, I highly recommend the Fingertips Passenger preference pane for managing your sites locally.
RAILS_ENV=production rake db:migrate
Now let’s add the Blueprint CSS framework. Download the latest version from blueprintcss.org and unpack it somewhere. Blueprint provides you with compressed versions of the CSS files, but humor me and add the uncompressed versions. From the unpacked directory, copy all the CSS files from /blueprint/src/ into /public/stylesheets/blueprint/ in your application.
Before we start up the application, let’s add the CSS files as well as the default JavaScript files to the head of our posts layout (/app/views/layout/posts.html.erb). The head of your layout file should look something like this (note that I added my own base.css file):
<head>
...
<%= stylesheet_link_tag 'blueprint/reset', :media => 'projection, screen' %>
<%= stylesheet_link_tag 'blueprint/typography', :media => 'projection, screen' %>
<%= stylesheet_link_tag 'blueprint/forms', :media => 'projection, screen' %>
<%= stylesheet_link_tag 'blueprint/grid', :media => 'projection, screen' %>
<%= stylesheet_link_tag 'blueprint/print', :media => 'print' %>
<!--[if lt IE 8]>
<%= stylesheet_link_tag 'blueprint/ie', :media => "screen, projection" %>
<![endif]-->
<%= stylesheet_link_tag 'base', :media => 'projection, screen' %>
<%= javascript_include_tag :defaults %>
</head>
Okay, now let’s fire up the application. I’ll be using Firefox so we can profile the application using YSlow. Go ahead and create your first post. Once you’re looking at the ‘show’ page, let’s open up Firebug and click on the “YSlow” tab. On the YSlow screen, click the “Run Test” button to get your page grade.
Bummer, we got an overall D – not so good. Let’s take a look at what’s going on. YSlow grades are listed in order of importance, so let’s check out the first section: “Make fewer HTTP requests”. Looks like we got a C in that area. What can we do to improve our grade? YSlow gives us some tips: “combine multiple scripts into one script, combine multiple CSS files into one style sheet”. Before we get back to the application, take a look at the “Components” tab in the YSlow dialog.
Hmm, five JavaScript files for a total of 234.3K and six CSS files for a total of 18K. We definitely need to work on that.
In order to compress JavaScript and CSS files in my applications, I use Scott Becker’s AssetPackager plugin. Go ahead and install it:
script/plugin install git://github.com/sbecker/asset_packager.git
The first step after installation is to create the configuration file for AssetPackager:
$ rake asset:packager:create_yml
You should see a message to reorder the files under ‘base’, so let’s go ahead and do that. Open up the newly-created /config/asset_packages.yml file. You’ll notice that there are two top-level entries – one for ‘javascripts’ and one for ‘stylesheets’. AssetPackager should have correctly generated the required files for the ‘javascripts’ section, but we’ll need to add the Blueprint files. Your completed asset_packages.yml file should look something like this (again, I added a base.css file):
--- javascripts: - base: - prototype - effects - dragdrop - controls - application stylesheets: - base: - blueprint/reset - blueprint/typography - blueprint/forms - blueprint/grid - base - print: - blueprint/print - ie: - blueprint/ie
Now that the config file is set up, you can go ahead and generate the combined, minified JavaScript and CSS files:
rake asset:packager:build_all
This command will output a “[name]_packaged” file for each entry under both ‘javascripts’ and ‘stylesheets’. Now we have to tell our application to use those compressed files. Go back to your posts.html.erb layout and change the head to look like this:
<head>
...
<%= stylesheet_link_merged :base, :media => "screen, projection" %>
<%= stylesheet_link_merged :print, :media => "print" %>
<!--[if lt IE 8]>
<%= stylesheet_link_merged :ie, :media => "screen, projection" %>
<![endif]-->
<%= javascript_include_merged :defaults %>
</head>
Okay, now it’s time to see the fruits of our labors. Restart the application in the Passenger preference pane, and reload the post page in Firefox. Now let’s run YSlow again. This time, you should see output like this:
Alright! We’ve improved our grade up to an overall B, with an A for “Make fewer HTTP requests”. Let’s take a look at the ‘Components’ tab.
Thanks to AssetPackager, we’re down to one JavaScript file for a total of 171.7K and two CSS files for a total of 12.6K. Now there’s eight fewer components, and we’re saving 68K of bandwidth on each request. Nice work!
The source code for this sample application can be found at: "github.com/dramsay/built_for_speed
“:http://github.com/dramsay/built_for_speed
Check back for more performance tips in the future.







