-
Notifications
You must be signed in to change notification settings - Fork 420
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Instrumentation - Skylight Integration #88
Merged
Merged
Changes from 6 commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
b57a084
add information on performance methodology
9bdd732
add oss metadata
be49170
rework of AS Notifications
hmcfletch 2a4cce5
taking a crack at the normalizers
hmcfletch e7c40af
Some docs for the instrumentation
hmcfletch f2ec5e0
do two separate rspec runs because of require issue
hmcfletch 1d52993
tear down the aliases
hmcfletch File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,3 +6,4 @@ rvm: | |
- 2.5.0 | ||
script: | ||
- bundle exec rspec | ||
- bundle exec rspec spec/lib/instrumentation/as_notifications.rb | ||
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
osslifecycle=active |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
require 'fast_jsonapi/instrumentation/serializable_hash' | ||
require 'fast_jsonapi/instrumentation/serialized_json' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
require 'active_support/notifications' | ||
|
||
module FastJsonapi | ||
module ObjectSerializer | ||
|
||
alias_method :serializable_hash_without_instrumentation, :serializable_hash | ||
|
||
def serializable_hash | ||
ActiveSupport::Notifications.instrument(SERIALIZABLE_HASH_NOTIFICATION, { name: self.class.name }) do | ||
serializable_hash_without_instrumentation | ||
end | ||
end | ||
|
||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
require 'active_support/notifications' | ||
|
||
module FastJsonapi | ||
module ObjectSerializer | ||
|
||
alias_method :serialized_json_without_instrumentation, :serialized_json | ||
|
||
def serialized_json | ||
ActiveSupport::Notifications.instrument(SERIALIZED_JSON_NOTIFICATION, { name: self.class.name }) do | ||
serialized_json_without_instrumentation | ||
end | ||
end | ||
|
||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
require 'fast_jsonapi/instrumentation/skylight/normalizers/serializable_hash' | ||
require 'fast_jsonapi/instrumentation/skylight/normalizers/serialized_json' |
22 changes: 22 additions & 0 deletions
22
lib/fast_jsonapi/instrumentation/skylight/normalizers/serializable_hash.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
require 'skylight' | ||
require 'fast_jsonapi/instrumentation/serializable_hash' | ||
|
||
module FastJsonapi | ||
module Instrumentation | ||
module Skylight | ||
module Normalizers | ||
class SerializableHash < Skylight::Normalizers::Normalizer | ||
|
||
register FastJsonapi::ObjectSerializer::SERIALIZABLE_HASH_NOTIFICATION | ||
|
||
CAT = "view.#{FastJsonapi::ObjectSerializer::SERIALIZABLE_HASH_NOTIFICATION}".freeze | ||
|
||
def normalize(trace, name, payload) | ||
[ CAT, payload[:name], nil ] | ||
end | ||
|
||
end | ||
end | ||
end | ||
end | ||
end |
22 changes: 22 additions & 0 deletions
22
lib/fast_jsonapi/instrumentation/skylight/normalizers/serialized_json.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
require 'skylight' | ||
require 'fast_jsonapi/instrumentation/serializable_hash' | ||
|
||
module FastJsonapi | ||
module Instrumentation | ||
module Skylight | ||
module Normalizers | ||
class SerializedJson < Skylight::Normalizers::Normalizer | ||
|
||
register FastJsonapi::ObjectSerializer::SERIALIZED_JSON_NOTIFICATION | ||
|
||
CAT = "view.#{FastJsonapi::ObjectSerializer::SERIALIZED_JSON_NOTIFICATION}".freeze | ||
|
||
def normalize(trace, name, payload) | ||
[ CAT, payload[:name], nil ] | ||
end | ||
|
||
end | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
# Performance using Fast JSON API | ||
|
||
We have been getting a few questions on Github about [Fast JSON API’s](https://github.com/Netflix/fast_jsonapi) performance statistics and the methodology used to measure the performance. This article is an attempt at addressing this aspect of the gem. | ||
|
||
## Prologue | ||
|
||
With use cases like infinite scroll on complex models and bulk update on index pages, we started observing performance degradation on our Rails APIs. Our first step was to enable instrumentation and then tune for performance. We realized that, on average, more than 50% of the time was being spent on AMS serialization. At the same time, we had a couple of APIs that were simply proxying requests on top of a non-Rails, non-JSON API endpoint. Guess what? The non-Rails endpoints were giving us serialized JSON back in a fraction of the time spent by AMS. | ||
|
||
This led us to explore AMS documentation in depth in an effort to try a variety of techniques such as caching, using OJ for JSON string generation etc. It didn’t yield the consistent results we were hoping to get. We loved the developer experience of using AMS, but wanted better performance for our use cases. | ||
|
||
We came up with patterns that we can rely upon such as: | ||
|
||
* We always use [JSON:API](http://jsonapi.org/) for our APIs | ||
* We almost always serialize a homogenous list of objects (Example: An array of movies) | ||
|
||
On the other hand: | ||
|
||
* AMS is designed to serialize JSON in several different formats, not just JSON:API | ||
* AMS can also handle lists that are not homogenous | ||
|
||
This led us to build our own object serialization library that would be faster because it would be tailored to our requirements. The usage of fast_jsonapi internally on production environments resulted in significant performance gains. | ||
|
||
## Benchmark Setup | ||
|
||
The benchmark setup is simple with classes for ``` Movie, Actor, MovieType, User ``` on ```movie_context.rb``` for fast_jsonapi serializers and on ```ams_context.rb``` for AMS serializers. We benchmark the serializers with ```1, 25, 250, 1000``` movies, then we output the result. We also ensure that JSON string output is equivalent to ensure neither library is doing excess work compared to the other. Please checkout [object_serializer_performance_spec](https://github.com/Netflix/fast_jsonapi/blob/master/spec/lib/object_serializer_performance_spec.rb). | ||
|
||
## Benchmark Results | ||
|
||
We benchmarked results for creating a Ruby Hash. This approach removes the effect of chosen JSON string generation engines like OJ, Yajl etc. Benchmarks indicate that fast_jsonapi consistently performs around ```25 times``` faster than AMS in generating a ruby hash. | ||
|
||
We applied a similar benchmark on the operation to serialize the objects to a JSON string. This approach helps with ensuring some important criterias, such as: | ||
|
||
* OJ is used as the JSON engine for benchmarking both AMS and fast_jsonapi | ||
* The benchmark is easy to understand | ||
* The benchmark helps to improve performance | ||
* The benchmark influences design decisions for the gem | ||
|
||
This gem is currently used in several APIs at Netflix and has reduced the response times by more than half on many of these APIs. We truly appreciate the Ruby and Rails communities and wanted to contribute in an effort to help improve the performance of your APIs too. | ||
|
||
## Epilogue | ||
|
||
[Fast JSON API](https://github.com/Netflix/fast_jsonapi) is not a replacement for AMS. AMS is a great gem, and it does many things and is very flexible. We still use it for non JSON:API serialization and deserialization. What started off as an internal performance exercise evolved into fast_jsonapi and created an opportunity to give something back to the awesome **Ruby and Rails communities**. | ||
|
||
We are excited to share it with all of you since we believe that there will be **no** end to this need for speed on APIs. :) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
require 'spec_helper' | ||
require 'fast_jsonapi/instrumentation' | ||
|
||
describe FastJsonapi::ObjectSerializer do | ||
include_context 'movie class' | ||
|
||
context 'instrument' do | ||
|
||
before(:each) do | ||
options = {} | ||
options[:meta] = { total: 2 } | ||
options[:include] = [:actors] | ||
|
||
@serializer = MovieSerializer.new([movie, movie], options) | ||
end | ||
|
||
context 'serializable_hash' do | ||
|
||
it 'should send notifications' do | ||
events = [] | ||
|
||
ActiveSupport::Notifications.subscribe(FastJsonapi::ObjectSerializer::SERIALIZABLE_HASH_NOTIFICATION) do |*args| | ||
events << ActiveSupport::Notifications::Event.new(*args) | ||
end | ||
|
||
serialized_hash = @serializer.serializable_hash | ||
|
||
expect(events.length).to eq(1) | ||
|
||
event = events.first | ||
|
||
expect(event.duration).to be > 0 | ||
expect(event.payload).to eq({ name: 'MovieSerializer' }) | ||
expect(event.name).to eq(FastJsonapi::ObjectSerializer::SERIALIZABLE_HASH_NOTIFICATION) | ||
|
||
expect(serialized_hash.key?(:data)).to eq(true) | ||
expect(serialized_hash.key?(:meta)).to eq(true) | ||
expect(serialized_hash.key?(:included)).to eq(true) | ||
end | ||
|
||
end | ||
|
||
context 'serialized_json' do | ||
|
||
it 'should send notifications' do | ||
events = [] | ||
|
||
ActiveSupport::Notifications.subscribe(FastJsonapi::ObjectSerializer::SERIALIZED_JSON_NOTIFICATION) do |*args| | ||
events << ActiveSupport::Notifications::Event.new(*args) | ||
end | ||
|
||
json = @serializer.serialized_json | ||
|
||
expect(events.length).to eq(1) | ||
|
||
event = events.first | ||
|
||
expect(event.duration).to be > 0 | ||
expect(event.payload).to eq({ name: 'MovieSerializer' }) | ||
expect(event.name).to eq(FastJsonapi::ObjectSerializer::SERIALIZED_JSON_NOTIFICATION) | ||
|
||
expect(json.length).to be > 50 | ||
end | ||
|
||
end | ||
|
||
end | ||
|
||
end |
56 changes: 56 additions & 0 deletions
56
spec/lib/instrumentation/as_notifications_negative_spec.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
require 'spec_helper' | ||
|
||
describe FastJsonapi::ObjectSerializer do | ||
include_context 'movie class' | ||
|
||
context 'instrument' do | ||
|
||
before(:each) do | ||
options = {} | ||
options[:meta] = { total: 2 } | ||
options[:include] = [:actors] | ||
|
||
@serializer = MovieSerializer.new([movie, movie], options) | ||
end | ||
|
||
context 'serializable_hash' do | ||
|
||
it 'should send not notifications' do | ||
events = [] | ||
|
||
ActiveSupport::Notifications.subscribe(FastJsonapi::ObjectSerializer::SERIALIZABLE_HASH_NOTIFICATION) do |*args| | ||
events << ActiveSupport::Notifications::Event.new(*args) | ||
end | ||
|
||
serialized_hash = @serializer.serializable_hash | ||
|
||
expect(events.length).to eq(0) | ||
|
||
expect(serialized_hash.key?(:data)).to eq(true) | ||
expect(serialized_hash.key?(:meta)).to eq(true) | ||
expect(serialized_hash.key?(:included)).to eq(true) | ||
end | ||
|
||
end | ||
|
||
context 'serialized_json' do | ||
|
||
it 'should send not notifications' do | ||
events = [] | ||
|
||
ActiveSupport::Notifications.subscribe(FastJsonapi::ObjectSerializer::SERIALIZED_JSON_NOTIFICATION) do |*args| | ||
events << ActiveSupport::Notifications::Event.new(*args) | ||
end | ||
|
||
json = @serializer.serialized_json | ||
|
||
expect(events.length).to eq(0) | ||
|
||
expect(json.length).to be > 50 | ||
end | ||
|
||
end | ||
|
||
end | ||
|
||
end |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If breaking out the spec is the only way to do it, chaining them with
&&
might be preferable (https://docs.travis-ci.com/user/customizing-the-build/#Customizing-the-Build-Step) as it would always generate testing output from both sets but still fail properly.That said, I think your test tear-down could alias the method back to the original and remove the newly added method easily enough and eliminate the need for the second running of rspec.