Friday, April 23, 2010

Rails has_many scoping and database indexes

If you are a good rails coder, than you probably form your associations like this:



class Parent < ActiveRecord::Base
  has_many :children
end

class Child < ActiveRecord::Base
  belongs_to :parent
end

Which is correct. Then, to keep things looking pretty, when you want a scoped subset of a given parent's children, you probably write something like this



class Parent < ActiveRecord::Base
  has_many :children
end

class Child < ActiveRecord::Base
  belongs_to :parent
  named_scope :who_can_drive,:conditions=>"age >= 16"
end

parent = Parent.first
children = parent.children.who_can_drive


Which is well and good. Except when you care about performance. You see, I did something like this in my production application. Both fields on the child model are indexed (the foreign key, and the field used for searching by), but guess which one gets used first? If you know your named scopes, you know that in practice the named scope that is APPLIED LAST in the chain is used first in the query, and that's the one MySQL decided to use as the primary index.

Now if a parent has 2 or 3 children, but it's in a table of 15,000,000 kids between the ages of 14 and 21, which index do you think will produce the smaller set to scan? The one that narrows it down to only the 2 or 3 children of that parent and then checks their age, or the one that narrows it down to the 7 million children over the age of 16 and then checks their parent?

So to keep my server from crashing and burning, I've started writing my code like this:



class Parent < ActiveRecord::Base
  has_many :children
end

class Child < ActiveRecord::Base
  belongs_to :parent
  named_scope :who_can_drive,:conditions=>"age >= 16"
  named_scope :belonging_to,lambda{|parent| {:conditions=>{:parent_id=>parent.id}}}
end

parent = Parent.first
children = Child.who_can_drive.belonging_to(parent)


I don't like it, but it sure improved my scores on my newrelic monitoring graphs. Anybody have a more elegant solution to this problem?

No comments: