Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Improving rendering performance with Jbuilder and Rails 3

The app I'm working on responds to most requests with JSON objects or collections thereof. We're using Jbuilder to construct those responses. The amount of data rendered is fairly large (several thousand objects in various nested structures - once formatted and fully expanded, there are as many as 10,000 lines of JSON for a typical response). This rendering is taking a significant amount of time - about 1/3 of the total request time, according to NewRelic.

I'm looking for some kind of guide, set of tips, or other resource that will help me make sure I'm getting the best possible performance out of JBuilder. I'm also curious if there are performance comparisons available for Jbuilder vs. RABL or other similar tools.

Edit: I've found a GitHub Issue that complains about Jbuilder performance, but the only actual suggestion anyone's made is 'don't use Jbuilder'. Well, actually, they used slightly stronger language, but there's still no word on why Jbuilder is so slow, what, if anything, can be done to get around it, or how other tools for the same task compare.

like image 899
MrTheWalrus Avatar asked Jun 11 '12 15:06

MrTheWalrus


2 Answers

jbuilder builds up a big hash containing your data and then uses ActiveSupport::JSON to turn it into json. There are faster json emitters as the following micro benchmark shows (make sure you have the multijson and yajl-ruby gems installed)

require 'benchmark'
require 'active_support'
require 'multi_json'
sample = {menu: {
    header: "SVG Viewer",
    items: [
        {id: "Open"},
        {id: "OpenNew", label: "Open New"},
        nil,
        {id: "ZoomIn", label: "Zoom In"},
        {id: "ZoomOut", label: "Zoom Out"},
        {id: "OriginalView", label: "Original View"},
        nil,
        {id: "Quality"},
        {id: "Pause"},
        {id: "Mute"},
        nil,
        {id: "Find", label: "Find..."},
        {id: "FindAgain", label: "Find Again"},
        {id: "Copy"},
        {id: "CopyAgain", label: "Copy Again"},
        {id: "CopySVG", label: "Copy SVG"},
        {id: "ViewSVG", label: "View SVG"},
        {id: "ViewSource", label: "View Source"},
        {id: "SaveAs", label: "Save As"},
        nil,
        {id: "Help"},
        {id: "About", label: "About Adobe CVG Viewer..."}
    ]
}}


MultiJson.engine = :yajl
Benchmark.bmbm(5) do |x|
  x.report 'activesupport' do
    1000.times {ActiveSupport::JSON.encode(sample)}
  end
  x.report 'yajl' do
    1000.times {MultiJson.encode(sample)}
  end
end

On my machine this produces

                    user     system      total        real
activesupport   1.050000   0.010000   1.060000 (  1.068426)
yajl            0.020000   0.000000   0.020000 (  0.021169)

ie to encode the sample object 1000 times active support took a hair over 1 second, MultiJson (using the yajl engine) took 21ms.

JBuilder is hardcoded to use ActiveSupport::JSON, but MultiJSON (a gem that lets you switch between json libraries) is a trivial drop in and is already a dependency of ActiveSupport - see my fork of jbuilder. I've opened a pull request, but until then you could try using this fork (or create your own - it's a one line change)

like image 149
Frederick Cheung Avatar answered Nov 15 '22 20:11

Frederick Cheung


Consider switching to Rabl and adding some caching. Given you have thousands of objects in nested structures, some nodes of your resulting JSON can be rendered as partials and cached - the performance gain can be huge.

Apart from this Rabl performance is slightly better than performance of JBuilder, but I find Rabl syntax sometimes confusing and I'd switch to JBuilder once it has fragment caching implemented.

like image 44
kulesa Avatar answered Nov 15 '22 20:11

kulesa