Grape Services & JSON Encode/Decode overhead

572 views
Skip to first unread message

Ajith Jayamohan

unread,
Feb 18, 2016, 10:27:21 AM2/18/16
to Grape Framework Discussion
Hello everyone,
We use Grape and Grape-Entity on all our REST services and recently got the point in the development where, although we can horizontally scale, we would like to improve the request/query per second each instance of our service can handle.

Towards that end, we started profiling our services with ruby-prof and noticed that a significant chunk of our overhead in JSON encoding/decoding.  Our Grape services return large collection of documents from the database backend as JSON records. I've attached a file to show the profiler classification of our call stack.

Observations
1. About 87% of the time is spent in Module::ActiveSupport::JSON.encode in the Grape::Middleware::Formatter#after.

2. Breaking that up further, Grape::Entity#serializable_hash receives 42% of the time spent. Note that in the case attached to the email, we are returning ~320 records of JSON document with about 40-50 keys in each record. 61% of the time here is spent in Grape::Entity#value_for exposure of delegates and precondition checks.

3. Bulk of this overhead, though, comes, thanks to the ActiveSupport::JSON::Encoding::JSONGemEncoder, 27.4% and 17.75%, amounting to nearly 52% of actual JSON encoding overhead.

Finally, the service code which does the business logic, authorizes user, retrieves the records from database, processes the record for consumption takes 12.36% of the overall request.

My questions are:
1. Is this common in Grape based API services?
2. Does the performance increase if Multi_Json Gem is used? How to tell Grape to use that? Looking at the middleware code, it looks like if the object responds to .to_json, multi_json isn't used and is left to the object to use its own encoder. In this case, these objects are ORM entities which will respond to .to_json. How to change the preference to use a high performance JSON encoder?
3. What are your thoughts on the Oj Encoder? How to make Oj encoder integrate with Grape?

If there are other ways (we can't use caching since data is real-time changing) to make this work, we would love to hear about that as well.

Thank you
Ajith
grape-performance.PNG

Daniel Doubrovkine

unread,
Feb 20, 2016, 11:25:30 AM2/20/16
to ruby-...@googlegroups.com
Multi_json is just a switcher between multiple json implementations, we've seen significant performance improvements by adding `oj` to the Gemfile and configuring mutli_json to use it, but YMMV, see https://github.com/intridea/multi_json. You should be able to just add oj to Gemfile and see it being used.

Generally returning large JSON bodies is expensive. You can only squeeze this much performance out of JSON rendering. I've personally always been a fan of redesigning an API around Hypermedia, returning many more small JSON payloads and constructing a URL structure in which you can cache things indefinitely (eg. an object gets a version, so your url always has ?v=1 and you "discover" the object by following links from a root that is never cached).

--
You received this message because you are subscribed to the Google Groups "Grape Framework Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to ruby-grape+...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.



--

Daniel Doubrovkine

unread,
Feb 20, 2016, 11:26:39 AM2/20/16
to ruby-...@googlegroups.com
Another way we have "solved" the problem was https://github.com/dblock/mongoid-cached-json, but I don't recommend it in 2016 ;)

Ajith Jayamohan

unread,
Feb 21, 2016, 9:09:53 PM2/21/16
to Grape Framework Discussion
Thanks, Daniel - We will give Oj and try -- Hypermedia redesign would have to wait until next release, too late to pull that andon cord. ;)

Ajith Jayamohan

unread,
Mar 1, 2016, 11:20:14 PM3/1/16
to Grape Framework Discussion
We added multi_json and Oj to the gemfile and deployed the services. In our profiler investigations, we still see ActiveSupport:JSON::Encoding called on arrays presented like so:

present customers, with: Customer::Entity

In the Grape formatter code, if the object responds to :to_json, it is preferred instead of MultiJson.dump - 
module Grape
  module Formatter
    module Json
      class << self
        def call(object, env)
          return object.to_json if object.respond_to?(:to_json)
          MultiJson.dump(object)
        end
      end
    end
  end
end


Since the array of customers responds to it, I'm assuming it is taking the path to ActiveSupport::JSONEncoder instead of Oj - Questions:

1. Is there a way to force MultiJson.dump or Oj.dump to be invoked instead of ActiveSupport::JSONEncoder
2. Overriding to_json in the model Customer or its Entity had little effect on the outcome. How to override the behaviour seen here?


Thank you

Ajith

Daniel Doubrovkine

unread,
Mar 2, 2016, 7:22:48 AM3/2/16
to ruby-...@googlegroups.com
Looks like you can tell ActiveSupport which encoder to use, see http://stackoverflow.com/questions/25262632/activemodelserializer-choose-json-encoder.

You can always roll out your own formatter via `format :json do` for example, at least to test with.
Reply all
Reply to author
Forward
0 new messages