Advanced Rails Studio Day 3 (Live Blogging)
The day starts with a list of potential topics. The class body votes on the topics that they want to cover. All of my votes went to ‘file uploads’ for work reasons – and fortunately this pushed it over the top! :D
And the results in order are:
- AJAX
- File Uploads
- Perf & Scalability
- Deployment
- AR with unsupported DBs
- Debugging
Notes after the jump:
AJAX & RJS
- Motherhood and apple pie describing RJS, Prototype, and Script.aculo.us at a high level
- DT: Believes that fewer web developers (well, he said “Rails”) are trying to emlate desktop applications with AJAX
- Me: Or those few that are do a better job? (i.e., Meebo)
ruby -e "self.javascript_guru?" => false
- Hopefully this doesn’t surprise anyone (again, motherhood and apple pie here) but: RJS generates JavaScript server side and sends the JS back to Prototype which evals the generated JavaScript which updates the DOM.
- For custom JS:
page << "raw javascript here"and it concatenates the JS in order with the other calls to page.
- Interesting to me: the “Old Way” involves generating DOM IDs in the calling view and the RJS – been there done that
- In Rails 2.0: the “simply_helpful” plugin provides a
div_forandtag_forthat generate the IDs for you. Sweet! - From the audience: JRails is a plugin that monkey-patches Rails to support JQuery. From toying with JQuery a little, I <3 it.
- In Rails 2.0: the “simply_helpful” plugin provides a
page.selecttakes a CSS selector and generates appropriate GS
page.select("div.queston").each do |q|
q.visual_effect ....
end
- I dig it.
- DT: Refactor into a helper if you’re going to reuse this (yup yup)
- page[:an_element].reload seeks out a partial “_an_element.erb” and updates the element
update_page { |p| p.alert("Hello!") }just generates a String- Can be called from controller, helper, or view
- CF: puts his RJS in the controller! His RJS code tends to be very short. If you’re doing more than that, then you’re probably making a busy page. (This is a handy take away)
Example:
render :update do |page|
page[:foo].your\_rjs\_operation
end
- To ‘easily’ support AJAX and synchronous:
respond_to do |format|
format.html { do something } # handles text/html http requests
format.js # causes lookup of RJS if making an AJAX request
end
- CF: Can call
update_pageas often as you want unlikerender- Makes it easy to mix JS, HTML, and Ruby in the view – but could get ugly
- So write helpers to be called via
link_to_function(Slide 205 and 206)
Audience remark: Testing a page that can respond to text/html and a JS request seems as though it can be brittle/hard to test
- CF: Yes, it’s harder to test a site that does AJAX than one that does not
- DT: Triple good point!: With AJAX, you have to consider how friendly your website is to screen readers. I hope that my current colleagues are reading this….
Writing your own RJS extensions:
- The JS generators uses
method_missingsopage.lightbox.dosomethingwould invokeLightbox.dosomethingin JS. - Be careful of the page size resulting from all of the code generated into the HTML
- The JS generators uses
break
File Uploads
Two basic solutions:
- attachment_fu plugin
- paperclip
- Biggest recipe in the Advanced Recipes book and freely available on the book site
Topics interesting to audience
- Performance (my question)
- Custom Mongrel handler
- CF: Should be about 10 lines of code
- MC: Code should be out there somewhere
- Merb (obvious solution but thicker than it used to be?) – started as a custom Mongrel handler
- Multiple concurrent uploads (also of interest)
- Me: See Ezra’s talk on this
- Progress bars
attachment_fu (yes, I’ve never used it. Shoot me.)
- In events_final web app:
- User has a Portrait
- Portrait
has_attachmentis monkey-patched into AR (See code sample in events_final) - Some columns in the DB are required by attachment_fu
- parent_id
- width
- height
- content_type
- filename
- thumbnail (can have several)
- Interesting notion of creating a logical model (in that it is not represented by a table in the DB) “PortraitService” (crappy name) delegate to:
- create a transaction to handle the tasks of:
- destroying an existing portrait
- assigning the portrait to a user
- saving the portrait
- handle updates to the portrait
Little disappointed that the PragProg guys weren’t prepared to discuss custom Mongrel handlers due to Rails single-threaded limitation. Mentioned it to Chad as a future add for the course. Merb wouldn’t even exist if this wasn’t a severe limitation with Rails; Ezra started it as just a simple custom Mongrel handler.
And, given my input, Chad is digging it into a little now. :D
- mongrel_cluster.yml
- config_script option allows specification of custom Mongrel handler
require 'mongrel'
class MyHandler < Mongrel::HttpHandler
def process(req, res)
res.start do |out|
out.write something # do something useful here
end
end
end
uri "/myuploadhandler", MyHandler, :in_front => true
- And just post to the URI specified in the custom handler.
- This will run in a separate thread (which is the point)
Performance and Scaling
Really only have conceptual knowledge of this myself so the nitty gritty examples here were really handy.
- Key points
- Measure performance often
- Identify hotspots
- Change one thing at a time
- Iterate
- This jibes with Ezra’s remarks about performance optimization when he first started talking about custom Mongrel handlers and led into Merb.
- Corollary: Guessing at performance bottlenecks is a waste of time.
- MC quoting Ezra: “Post-mature optimizations are the cost of most hosting bills”
- Optimizing AR
- Look at the log files
- Obviously, AR-generated queries could be ugly – so look at log files early
- DT: Requests/second is the most useless statistic in the log file
- DT: AR makes life simple – and sometimes simple is stupid
- Selective Columns
- Custom finders:
- Use the :select option to specify the columns to pull.
- Pull the ID column to avoid possible silent DB failure (DB specific)
- Preloading Child Rows
- For N + 1 queries (i.e.
has_many :line_itemsthat may have products itself)- Use of
:include => :product - MC mentioned yesterday, take care with use of :include
- DT: Please, please, measure when using :include. It may not help performance.
- MC: suggests having separate associations for explicitness (readability and performance)
- Use of
has\_many line\_items has\_many line\_items\_with\_products :include => product
- Assigning query results to a constant in an AR class that will load the results at startup and cache the results in the object model
- Great for things that don’t change such as countries, states, etc.
- ActiveRecord Query Cache
- Based in identical SQL
- Cleared on CUD
Event.cache do |e|
e.find(1) # DB
e.find(1) # Cache
e.update
e.find(1) # DB
end
- Cache Store (Rails 2.1 – so still Edge)
- API for configuring default store
- Supports memory, file system, DRb (WTF?), memcache
- So I ask: For memcache, this means a shared cache for a Mongrel cluster?
- MC: yes
- Sweet! (Yes, I loved Dude, Where’s My Car)
- API for configuring default store
store = ActionController::Base.cache_store
store.write(key, value)
stuff = store.fetch(key) do
ARClass.stuff(key)
end
- Note that this is not just for ActiveRecord
- Allows for caching across requests
- Even more complex examples on slide 309
lunch
- Page Caching
- Pages that don’t take any parameters
- Pages that don’t change
- Cron job for cleaning up cache
- Can’t cache
- Personalized pages, pages w/ params, pages w/ content changes, time-based content
- Although, for the latter, you could cache and force a refresh by destroying the cache periodically…
- Personalized pages, pages w/ params, pages w/ content changes, time-based content
class SomethingsController < ApplicationController caches_page :index, :show end
- Fragment Caching
- Cache small parts of templates
cachecan be further parameterized to cache per user, etc.- Expiring specification is available in 2.1
- But also via plugin: (see Karen Gillison)
<% cache('some label') do -%>
Your HTML goes here
<% end -%>
Expiring caches
- expire_page
- expire_action
- expire_fragment
Sweepers
- For cleaning up caches
- Observes models
- Lifecycle actions on Sweeper get invoked when the lifecycle actions on the observed Model get invoked
- Slide 319
- In the controller, have to specify the Sweeper (see below)
- To avoid having observers impact performance of transactions, put the expirey on a queue and operate on it later.
- Me: If this were J2EE, shove it into a JMS queue – but J2EE…. ick.
DT: Complaining about the number of moving parts that are now being integrated into Rails: memcached, Starling, multiple Mongrels, Ferret, etc. Deployment is more painful than J2EE (Sadly, agree violently). Waiting for a backlash to occur at the complexity of it. Look at Twitter and how often it fails due to some SPOF (single point of failure). People writing many of the tools/plugins do not have enterprise experience and so are not taking external dependencies into account.
class MyController.... cache_sweeper :my_sweeper end
Memcache
- Basically an in-memory hash (that you do not always need to use depending on your app)
- cache_fu at the model level
- supports TTL
- Probably less relevant as of Rails 2.1
HTTP Performance
javascript_include_tag(:all, :cache => true)- Combines (and possibly compresses) JS files
ActionController:Base.asset_host = 'asssets%d.example.com'- Allows for federating assets across servers to avoid browser-limited number of connections per server
- Yslow Firebug plugin (Andrew Willis mentioned this to me at lunch on Monday)
- Tools
- httperf
- Siege
- Profiler
General Points
- Don’t use file-based sessions
- Don’t forget indices
- Watch the log
- Cache where useful
DT: Consider adding some automated monitoring tools to determine when the system is beginning to perform badly
break
- Worth noting that AOL Photos uses Rails – if you’re looking for another example of a large company relying on Rails.
Deploying
- Deploy early
- Me: It was personal development, but I was impatient to deploy the first (and only so far) time that I’ve used Capistrano. Better if I had tried to deploy earlier to forego that. Similar to TDD/BDD: makes the behavior less painful.
- How many Mongrels in a cluster?
- MC: Varies but fewer than you think
- Question about not needing Mongrels
- CF/DT: mod_rails – still in beta
- Ruby processes that Apache comunicates with via *NIX sockets
- Forking a process – so as some point out its similar to FastCGI
- MC: There’s also Thin, Ezra’s work tying Rack and Rails together
- MC: They started by using Apache 2.2.x
- mod_rewrite
- mod_proxy_balancer
- Nginx
- Small memory footprint
- Easier to configure than Apache
- MC: unless you have a damn good reason, pick Nginx (I need to use this beast – been hearing about it for over a year…)
- “Heartbeat”
- Custom Mongrel handler excellent for this
Posted by evan on Wednesday, May 07, 2008
blog comments powered by Disqus




My name is Evan Light and, yes, I am a nerd. I'm also a professional software developer who, after spending one too many years contracting to the federal government, escaped into the far more enjoyable commercial world. Having spent several years using C and even more using Java (the latter very nearly caused me to give up programming entirely), I consider myself fortunate to have discovered Ruby and to use it as part of my daily work.