The price of comfort

I am serving a ton of data to Angular for my app Graffito, like 3000 geocoded ‘graffiti incident’ reports from NYC Open Data via the SODA API. I use the data to plot markers on Google Maps, as well as overlay a heatmap with Heatmap.js.

Performance has always been an issue.

One way I’ve improved user experience is to plot the map only once, and use jQuery to toggle its visibility depending on the route. Angular’s UI-router has a nifty OnEnter callback to manage that gracefully.

Today, I focused on improving performance via comparing different query implementations –  JBuilder vs. customizing Rails’ as_json(options={}) method. JBuilder provides a DSL that can be used stand-alone or directly as an ActionView template language.

Let’s compare request times by descending order of duration.

I use rack-mini-profiler to audit performance. It creates a handy little window in your browser for assessing the performance of each query in the request with caching disabled.

JBuilder in a template (index.json.jbuilder):

Screen Shot 2016-08-26 at 3.25.11 PM

JBuilder as standalone DSL:

Screen Shot 2016-08-26 at 2.59.13 PM

as_json patch:

Screen Shot 2016-08-26 at 2.58.56 PM

Wow. The extra overhead JBuilder imposes on your app as a template language is huge – about 10 seconds more than customizing Rails’ as_json. The the time it takes to render index.json.jbuilder is impractical for my huge data set.

But even as a standalone language, JBuilder is slower by nearly one second. And this makes sense, JBuilder essentially wraps Rails in another language.

The argument for using JBuilder is comfort – a DSL is more semantic. But in my experience, that’s only when using it as a template language. As a standalone DSL, its less clear than Rails.


# customizing as_json with standalone JBuilder DSL
def as_json(options={})
Jbuilder.encode do |json|
graffito = self
json.(graffito, :incident_address, :borough, :latitude, :longitude)
json.upvotes(graffito.upvotes.count)
json.upvoted_by(graffito.upvotes.pluck(:user_id))
end
end
# Customizing as_json with plain old Rails
def as_json(options={})
graffito = super(:only => [:incident_address, :borough, :latitude, :longitude])
graffito[:upvotes] = upvotes.count
graffito[:voted_by] = upvotes.pluck(:user_id)
graffito
end

JBuilder introduces the unfamiliar class method encode, initializes a json object, and sets the graffito attributes to that object. It takes some brain power to figure that out, slowing down the development process.

It’s worth nothing that rails does all this too under the hood with Active Model Serialization. That’s why when we call super, it returns a json object. Both JBuider and Rails do it in O(n) time, but Rails does so more efficiently, without a DSL wrapper to slow everything down.

I’m glad I compared these approaches. For now, I’m sticking with plain Rails. Only when an API becomes super vast, with many levels of nesting, would I pick a semantic DSL like JBuilder, and even then, I’d only use it as a template language for smaller queries.