Paging Dr. Cache

When I launched this blog last week I thought it was slow because of my small (256MB) Slice at Slicehost. Then I read about people optimizing their rails installations, so I optimized and it got a bit faster. But, it was still slow. So I read up on optimizing the speed and memory usage of Phusion Passenger aka mod_rails. I made some enhancements and it got a bit better. Problem was, it was still REALLY slow. For a second I got a bit discouraged, I thought to myself “Is this the kind of performance I can expect from a simple rails app?”, I quickly reminded myself that I was probably just not realizing that there was something I could do to make things faster. Then I remembered something I read about caching.

Page Caching is EZ

Following the super-duper-awesome page caching tutorial on the RailsEnvy blog, I quickly setup some caching for my posts controller. I also got my sweepers happening to make sure I’m not serving my guests anything old and stale (bad host indeed).

1
2
3
4
5
class PostsController < ApplicationController
...
  caches_page :index, :show
  cache_sweeper :post_sweeper, :only => [:create, :update, :destroy]
...

All together it took about 15 minutes, I spent some extra time troubleshooting a problem that was the result of naming my sweeper PostSweeper.rb rather than post_sweeper.rb.

mod_rails configuration

In the RailsEnvy tutorial they mention setting up some custom mod_rewrite rules to handle telling Apache to serve your cached pages instead of passing the requests on to your mongrel instance. From what I read in the Passenger/mod_rails user guide, this wasn’t necessary, plus they mention that mod_rails doesn’t play well with mod_rewrite anyhow, so I didn’t change anything about my vhost config.

PLS 2 BEE BENCHMRKIN

I read all about how cool and fast page caching was and not that I didn’t believe it, but I was curious how cool and fast it really was. To test any performance improvements I used Apache Benchmark (ab for short).

The test consisted of running 500 requests with a concurrency level of 5

1
$> ab -c 5 -n 500 http://www.lengelzigich.com/

I ran this three times from start to finish, waiting about 30 seconds between each run. Then I ran it a fourth time and saved those results.

Before Page Caching

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
Server Software:        Apache
Server Hostname:        www.lengelzigich.com
Server Port:            80
 
Document Path:          /
Document Length:        4599 bytes
 
Concurrency Level:      5
Time taken for tests:   92.220691 seconds
Complete requests:      500
Failed requests:        2
   (Connect: 2, Length: 0, Exceptions: 0)
Write errors:           0
Total transferred:      2552000 bytes
HTML transferred:       2299500 bytes
Requests per second:    5.42 [#/sec] (mean)
Time per request:       922.207 [ms] (mean)
Time per request:       184.441 [ms] (mean, across all concurrent requests)
Transfer rate:          27.02 [Kbytes/sec] received
 
Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:       74  392 2023.8     78   19114
Processing:    96  115  34.8    103     536
Waiting:       94  111  24.8    100     207
Total:        172  508 2022.9    183   19215
 
Percentage of the requests served within a certain time (ms)
  50%    183
  66%    187
  75%    199
  80%    223
  90%    249
  95%    276
  98%   7114
  99%  11201
 100%  19215 (longest request)

After Page Caching

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
Server Software:        Apache
Server Hostname:        www.lengelzigich.com
Server Port:            80
 
Document Path:          /
Document Length:        4602 bytes
 
Concurrency Level:      5
Time taken for tests:   24.150369 seconds
Complete requests:      500
Failed requests:        0
Write errors:           0
Total transferred:      2553500 bytes
HTML transferred:       2301000 bytes
Requests per second:    20.70 [#/sec] (mean)
Time per request:       241.504 [ms] (mean)
Time per request:       48.301 [ms] (mean, across all concurrent requests)
Transfer rate:          103.23 [Kbytes/sec] received
 
Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:       74   88 180.5     78    4008
Processing:    95  119  36.3    105     494
Waiting:       93  116  35.4    103     492
Total:        171  208 183.2    186    4109
 
Percentage of the requests served within a certain time (ms)
  50%    186
  66%    192
  75%    202
  80%    218
  90%    247
  95%    261
  98%    277
  99%    392
 100%   4109 (longest request)

The Results

  • 4x improvement in requests per second
  • Time per request reduced by 75%
  • 100% of requests were served in 4 seconds or less compared to 15 seconds or less

Overall I’m more than pleased with the results and plan on taking a look at the next RailsEnvy tutorial on ActionCaching.

Instant Gratification through Unanswered Posts

I’ve been a member of Rails Forum for over a year now, since January 2007 to be exact. In my first year and 3 months I managed to make about 90 posts. Looking back these are mostly “newb” questions and solicitations for getting on the right track. This was when I was casually using rails and getting up to speed. In the last two weeks I’ve made about 40 posts, 95% of which have been answering the same type of questions that I was asking last year. This type of reciprocal system is a great way to learn and instant gratification.

Unanswered Posts

Most forums have an unanswered posts link that show you all of the posts where no-one has yet replied (duh). I’ve found this to be a great way to become a “first responder” to a lot of problems. Not only do I get a shot at answering a question in my own way, without being biased or lead by other member’s answers, but I get some satisfaction from seeing a list of recently answered posts all with my name on them.

Teaching as a Learning Tool

Personally I’ve always found that teaching or explaining something to someone is a great way to improve my personal understanding of that particular topic. This has been an important part of improving my knowledge of the “why” in all of this. It’s easy to reply to a person and give them the answer, but providing the “why” component, or having to go look it up and then provide it, make a huge difference in furthering my whatever fu.

Get Answering!

  1. Pick a topic or skill you’re learning more about
  2. Find a community
  3. Start answering
  4. ???
  5. Mentally profit

Learning Rails: Namespaced Controllers

A common requirement of a visitor facing web application is that the administration component of the app is behind a login with a different layout, style sheet and feel. Rails 2.0 makes this very simple with namespaced controllers. On a recent project the specs called for the administration to live under an /admin path, simple enough.

Creating the Admin Interface

1
$> script/generate controller admin/employees index show new create edit update

Perfect, now I’ve got my new controller and some views.


  /app/controllers/admin/employees_controller.rb
  /app/views/admin/employees/
    /app/views/admin/employees/index.html.erb
    /app/views/admin/employees/show.html.erb
    /app/views/admin/employees/new.html.erb
    /app/views/admin/employees/edit.html.erb

We’ll need to create a new layout and style sheet for our admin interface:

1
2
$> touch app/views/layouts/admin.html.erb
$> touch public/stylesheets/admin.css

A quick edit to our routes file and everything is in place:

1
2
3
map.namespace :admin do |admin|
  admin.resources :employees
end

In a few short steps we’ve got everything we need for our /admin area. Now we just have to build out all of the controller actions, write all of our tests, add the markup to the views… and then we have to do it all over again for the public facing controller.

The Drawbacks

At this point I realized how un-sexy this technique is. For each Model that I want to display to the public and edit in the admin I have to create two controllers, two views, two layouts, two style sheets and twice as many bugs, typos and frustrations. GAH. By the time you write all of the specs for each controller you’ve got some ridiculous code:test ratio and you never want to look at TextMate again.

The Expense of “Keepin’ it DRY

As a rails developer you should always think about “Keepin’ it DRRRYYYY” but sometimes the pursuit of DRY just isn’t worth it. As far as I can tell, keeping the admin and public controllers, views and tests separate and DRY adds way more complexity and confusion when you use namespaced controllers. I just don’t think it’s worth it

Alternatives

  1. You can create named routes to satisfy the “/admin” requirement
  2. Write an application controller method to determine which template to render (based on admin/public visitor status)

That’s it really, it doesn’t take much to keep things simple® and avoid a bunch of duplicate-in-concept code. The next time you reach for namespaced controllers to satisfy this type of functionality, think twice, it might save you a lot of trouble.

Reinventing Wheels

It’s been about two weeks since I started writing the ruby on rails app for this blog. I recently took a look at some of the established RoR blogging engines and this feels more like a somewhat circular rock. Even though this app is lacking a lot of the regular features of a blog, I’m extremely happy with how it turned out.

Build to Learn

One of the things that I realized early in my programming career was that reading about how to do something was nice, but I never felt like I really understood it. However, when I started actually building something everything started falling in place. I think this is a pretty standard realization for a lot of developers and it’s always something I keep in mind when learning a new technology. Enter Ruby on Rails. Even though I read a ton of stuff, watched lots of videos, subscribed to podcasts and purchased expensive books, I didn’t realize how much I was missing until I set out to plan, build and deploy this application.

Referencing the Reference Books

By a huge margin the best ruby on rails book I found was The Rails Way (non affiliate link btw). There was just something about it’s straight forward nature, clever writing and almost-but-not-quite reference book feel that made it so easy to use. There was a great post on the Signal v Noise blog the other day about years of irrelevance where David spoke about how it took him some time to get up to speed with ruby’s syntax, idioms and just how things worked. I think I’ve finally reached that point with rails, the more and more I used the The Rails Way the less and less I needed it. Combine this awesome book with building this app and I think I made it through the learning curve faster than I would have following tutorials or watching videos.

What I Learned

Here’s just a short list of things I learned while building this app. I plan to expand this particular concept into a series of posts.

  • Using RSpec for BDD Testing
  • RESTful controllers
  • Ruby syntax and idioms
  • Customizing and extending Rails
  • Deployment with Capistrano
  • Git

Sadly I had spent some time already reading about all of these, but never understood how far off I actually was.

When NOT to Reinvent

While I’m hailing the virtues of building your own stuff from scratch it’s important to know when “rolling your own” is a bad idea. First, if you’re doing work for a client chances are you’re going to increase your profitability by finding a solution that’s been built and tested and proven to work, do you really want to build your own Authorize.net processor when you can use Active Merchant instead? Secondly, if you don’t have the time and patience to work on your project with the understanding that you’re trying to learn as you go, you run the risk to slapping together something that’s a hodgepodge of bad code and shortcuts, which will be more trouble down the road.

App Delivers; Would do Again

Like I said earlier, while I don’t have everything I’d like in this app, I’ve learned more doing this than I would have reading about how to do this so I’m happy. I know everything about this app and can add to and improve it as time goes on. Perhaps most importantly I have the satisfaction of having built something that’s now facing the world which is one of the more underrated accomplishments in software development.