The performance of .to_json in Rails sucks and there’s nothing you can do about it

Originally posted on the Agworld Developers Blog, hosted on Tumblr, on February 9, 2013.

Jason Hutchens
The Magic Pantry
3 min readMay 30, 2022

--

As the application we’re working on evolves away from a monolithic Rails app and towards a loosely-coupled collection of services, we find that we’re dealing more and more with large JSON blobs.

Recently it became apparent that the performance of our JSON wrangling wasn’t up to snuff. Which was weird, because I proved to myself quite early on that we were most definitely using the json/ext variant of the json gem, which is implemented in C, and should be plenty fast enough.

Just to make sure, I fired up a Rails console and checked. Yep, json/ext was in play. So I then ran an experiment to make sure that it was as fast as I supposed, by writing a simple test:

Running this from the command line, outside of rails, convinced me of the speed advantage:

We should have be getting reasonable JSON performance in Rails. But we weren’t. To investigate further, I ran the same test with rails runner instead (which is pretty much the equivalent of copy-pasting it into the rails console; it basically fires up Rails before executing your code). This is what I got:

Whaaaa? Abysmal performance. And changing the test to use yajl/json_gem made no difference to performance when the test was performed within our Rails app.

A bit of investigation revealed the culprit. In 2010, in order to fix a low priority bug (that ActiveModel::Errors#to_json generated invalid JSON), the following code was introduced to active_support:

Mmmm-mmmm, I love me my hacks. Here, the json gem is shanghaid into loading, even if you are deliberately requiring it after rails when trying to figure out how to make your .to_json performance not suck. Then, the to_json method is patched for all common classes, including Array and Hash, effectively neutering it.

You’ll find this code in active_support/core_ext/object/to_json. If you do anything in your app to cause that file to be parsed (such as, say, using the authlogic gem, like we do, or, say, using active_record) then your JSON performance will be irrevocably poor if you use .to_json on objects to JSONify them.

Yes, I know you can generate JSON in other ways that wouldn’t fall foul of this hackery. But we use .to_json a lot, and it’s not just us; many of the gems that we use also use .to_json. Indeed, there are 40,000 public repositories on GitHub that use .to_json in Ruby code. It’s popular. And gems or frameworks shouldn’t neuter these kinds of things if they can at all help it.

I reported a bug, although I’m pretty sure it’ll be a hard fight to get any kind of fix through. I reckon the original bug should have a more specific fix that doesn’t nix JSON performance for everyone. If that’s not possible, then the existing fix should be made optional, with a flag in configuration for disabling it. We’ll see how it goes.

In the meantime we’ve monkeypatched around the problem by applying a similar change as the one in active_support, but this time using multi_json and oj (which is the fastest and the most rails-compatible JSON gem I could find) in concert.

And we’re happy to report that, in initial tests, we’re seeing JSON views render in 8ms instead of 500ms. Good times!

This article was originally posted on the Agworld Developers Blog, hosted on Tumblr, on February 9, 2013, and is almost certainly out of date by now.

--

--