Accessing your database from Rails Metal

I was interested in using the new Rails Metal system for a project I’ve been building. What I want this code to do is take parameters out of the URL and write a record to my database to record these parameters.

This is my first application built with Rails 2.3, and the first application in my company to use Rails Metal in a production environment.

Here’s what I wrote:

  def self.call(env)
      if env["PATH_INFO"] =~ /^\/i\/(\d+)\/(\d+)\/(\d+)\/(\d+)/
        Record.track($1.to_i, $2.to_i, $3.to_i, $4.to_i)
        [ 200, { "Content-Type" => "text/html" }, [ "OK" ] ]
      else
        [ 404, { "Content-Type" => "text/html" }, [ "Not Found" ] ]
      end
  end

This code works great. Runs fast, does what I need.

Until I run it in production under some load!

Suddenly, I see requests backing up and monit is killing my Mongrel’s as the heartbeat call is timing out!

Diagnosing this problem was a bit painful. The RAILS_DEFAULT_LOGGER isn’t available, so I have to create my own logger, and then I used Benchmark.measure to writing out some timings.

The one thing I see is that every 5 calls, a call stalls for exactly 5 seconds. My superfast recording system is taking on average 1 second per request!

The number 5 rings a bell – where did I see that recent;y? I remember now, its in the database.yml file. There’s a new setting for connection pooling. I wonder how that works?

Well, it turns out that Rails Metal really doesn’t set up anything for you, nor does it tear anything down at the end of the request. For Connection Pooling the connection is setup automatically when you access your database, but outside of a Rails Controller, you’d better release this connection before you’re done.

Here’s a simplified version of how I did this:

  def self.call(env)
    if env["PATH_INFO"] =~ /^\/i\/(\d+)\/(\d+)\/(\d+)\/(\d+)/
      begin
        Record.track($1.to_i, $2.to_i, $3.to_i, $4.to_i)
      ensure
        # Release the connections back to the pool
        ActiveRecord::Base.clear_active_connections!
      end
      [ 200, { "Content-Type" => "text/html" }, [ "OK" ] ]
    else
      [ 404, { "Content-Type" => "text/html" }, [ "Not Found" ] ]
    end
  end

Posted by Tom Fri, 08 May 2009 03:34:00 GMT


Performance with MySQL and paginating large tables

If you’re using the will_paginate Rails plugin to manage the amount of data that you show to the user on a single page, there is a gotcha that comes into play as your data gets bigger.

The default display of the pagination buttons for shows links that can take you to the last page of the set. If you only have a few thousand records, you’ll never notice the problem, but try it with a million records and check out the MySQL slow log file.

The SQL query generated by your paginate call will look something like:

  SELECT * from foos ORDER BY created_at LIMIT 1000000,50

In the mysql-slow.log file you’ll eventually start seeing an entry that looks like this:

  # Query_time: 6  Lock_time: 0  Rows_sent: 50  Rows_examined: 100050
  SELECT * from foos ORDER BY created_at LIMIT 1000000,50

The key here is that MySQL had to look at 1M rows to return the 50 you requested!

This is a Very Bad Thing.

You must do all you can to avoid this case. What you do will depend on your specific application. The easiest is to not let page numbers go above a certain maximum, and configure the will_paginate view helper to not show all the possible page numbers.

Posted by Tom Fri, 27 Mar 2009 06:17:00 GMT


Rails named_scope and fragment caching

The typical way I’d write code that used fragment caching looked like this.

Contoller

def foo
  unless read_fragment('key') do
    @data = Model.get_some_data
  end
end

View

<% cache('key') do %>
    <%= do_something_with @data %>
<% end %>

The fragment caching adds extra work to the controller code to avoid doing the work that didn’t need to be done due to the data being cached. This is kind of messy.

I’ve also written code like this to avoid the above cruftiness

<% cache('key') do %>
    <% @data = Model.get_some_data %>
    <%= do_something_with @data %>
<% end %>

From my previous post about when the named_scope queries actually run it occurred to me that the following code now works correctly.

Contoller

def foo
  # No query gets executed here!
  @data = Model.some_data.since(24.hours.ago).limited(10)
end

View

<% cache('key') do %>
    <%= do_something_with @data %>
<% end %>

The Named Scope code allows my controllers to once again not care about how the View code works.

Posted by Tom Fri, 05 Dec 2008 05:12:00 GMT


Real money in online game economies

I recently read Play Money, a book about how Julian Dibbell spent a year trading virtual goods in Ultima Online with real money. This is an interesting read for anyone building any sort of online system that has virtual goods of any kind. Even if you don’t want people to trade items in your world, people will find a way to do it, and some may make money from the process, and others will be ripped off (and blame you for it too!)

The cool thing is that if you have any success at all, there will be a demand for this type of trading that you can take advantage of – you just need to plan it out to make it transparent to users and not allow holes that cheaters can take advantage of.

Posted by Tom Sun, 30 Nov 2008 00:05:00 GMT


My company is looking for a programmer

Check out our Craigslist posting

We’ve had a lot of response from Microsoft stack people, some Java, a few PHP people and a few people with no programming experience at all.

We got really lucky with our first attempt a few months ago – we ended up hiring the first person to respond to the listing (which is kind of scary). Perhaps now we’re hitting the reality of hiring people this way.

We’re looking for people to build social networking games in Rails, so any combination of some of these skills would be a great start. So far, no-one with Rails or Social Networking experience has responded.

If anyone is looking to work for a cool new company, building Social Games in Rails in the Seattle area (we really need lots of face time), get in touch!

Posted by Tom Sun, 09 Nov 2008 01:36:00 GMT


High Performance MySQL - now with more Kick Ass!

I have the first version of this book, and have just used parts of this, the second version, to help setup a new MySQL server in a production environment.

This book has saved me hours already. Worth the price, right there.

If you do stuff with MySQL, you must have this book.

Posted by Tom Fri, 27 Jun 2008 04:05:00 GMT


jPod, the TV Show!

I recently read jPod the book by Douglas Coupland. Its a pretty good book, kind of weird – perhaps Quirky is a good description.

As a programmer in the video game industry, its kind of required reading

It turns our that CBC in Canada turned this into a TV Show)

This story works much better as a TV show!

Posted by Tom Fri, 27 Jun 2008 03:55:00 GMT


ATOP is my favorite Linux tool

I’m diagnosing a load problem with a server, and ATOP has helped me a lot.

For Ubuntu – ‘apt-get install atop’ works fine.

Here is the ATOP home page

Posted by Tom Sun, 08 Jun 2008 04:14:00 GMT


MySQL Data type changes which index to use

I tracked down a performance issue in my code today that was pretty subtle.

I have a table with a varchar column that contains a number, mainly for size reasons. This column has an index all to itself, so queries using the column should be pretty snappy, but they weren’t.

My code went something like this:

  find(:all, :conditions => "my_col = #{value}")

I know I shouldn’t have built the conditions that way, but that’s the way the code was. And the performance sucked!

Since my_col is actually a string, I need quotes around the value otherwise MySQL has to try to figure it out, and it misses the index completely!

So, in future, use the :conditions the correct way, as Active Record does a pretty good job of getting the quotes right!

So, why did I write the code this way?

The code I actually wrote is for the parameters to a call to update_all, and the documentation for update_all isn’t as clear as it could be on how the parameters work. The key is that both of the first 2 parameters to update_all are the same as a :conditions parameter to find (but without the :conditions symbol). So they can be a string, or an array that has Active Record parameter substitution.

Posted by Tom Thu, 05 Jun 2008 04:14:00 GMT


Deleting a Facebook app is far too easy

In the Facebook Developer Application, the act of deleting an application does have a confirmation page, but it isn’t very clear on what application is being deleted – there is the name of the application, but not the picture or icon.

And the delete works even if you have thousands of users.

Ask me how I found this out, I dare you!

Posted by Tom Thu, 29 May 2008 07:38:00 GMT