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:
- ParentObject has_many ChildObjects
- Each ChildObject has_many OtherObjects
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.