iprog.com

Faster alternatives to ActiveRecord::Base.to_xml (Rails Performance Series)

This is part 5 of my ongoing series on Ruby on Rails performance. Today’s topic is a bit more complicated that some of the previous parts.

I’m going to explore an alternative to using the method 1ActiveRecord::Base.to_xml.

Before I get into that, I probably should explain why. After all, 1ActiveRecord::Base.to_xml is really easy to use. The alternative I’m going to demonstrate isn’t very easy to use.

The problem is that 1ActiveRecord::Base.to_xml can be really slow—even after making the optimizations I’ve explored in the previous parts to this series. For fairly simple AR objects, this won’t be a problem. However, when you have a deep object hierarchy and need substantial portions of it to be included in the XML response, it becomes a problem.

One instance where I encountered this was a nested has_many tree. That is, the object tree looks something like this:

The XML output is often 100k or more, and representing a few dozen or more total objects.

With that scenario, the 1ActiveRecord::Base.to_xml call looks something like this:

1@parent_object.to_xml(:include=>{:child_objects=>{:include=>:other_objects}})

The alternative is definitely more work:

 1h = {:attr1=>parent_object.attr1, :attr2=>parent_object.attr2} # ... etc
 2
 3h[:child_objects] = parent.child_objects.map do |child|
 4  h2 = {:attr1=>child.attr1, :attr2=>child.attr2} # ... etc
 5
 6  h2[:other_objects] = child.other_objects.map do |other|
 7    h3 = {:attr1=>other.attr1, :attr2=>other.attr2} # ... etc
 8  end
 9
10  h2
11end
12
13h.to_xml(:root=>"parent_object")

Note that we’re actually still using a form of 1to_xml — it’s just 1Hash.to_xml instead.

There are some ways this could be streamlined, such as using 1parent.attributes to pull the list of attributes, or even an array of wanted attributes. I skipped that here to make the example easier to understand.

This method does have a couple benefits. One is that you can use more intelligent logic in regard to selecting the 1child_objects to include. For example: 1parent.child_objects.all(:limit=>10, :order=>'id desc') if you wanted just the last ten child objects.

Beyond that, and the focus of this posting, I found that the above resulted in a 2-3x speed increase. While I haven’t dug really far into the matter, it appears that the Rails magic that figures out what associations to include and the associated reflection has a heavy overhead.

I still use 1ActiveRecord::Base.to_xml much of the time, but the above option is nice for frequently requested actions with large, deep object trees.

tags: activerecord, performance, xml, ruby, rails