RailsをAPIサーバーとして動かすにあたり,どのようなライブラリを用いるのが良いのか調べた.
実際にパフォーマンスを簡単に測定したのでお伝えします.
参考リンクを末尾に記載するので結論の裏どりが欲しい方はそちらも参照ください.
TL;DR
FastJSON API + Oj でJSONを返すのが最もパフォーマンスが良い
前提
JSONとはJavaScript Object Notationの略で,JSのObject型をStringで表現したものである.
String型なのでブラウザに受け渡しができる一方,
サーバー側ではObjectデータとして取り扱いやすいことから,広くAPIで一般的に用いられるデータの形となっている.
リクエストを受け取ったサーバーが,必要なデータをObjectからStringに変換する作業のことをJSON Serializationと呼ぶ.
このSerializationには大きく2つのプロセスに分解できる.
- Data Preparation: サーバーサイドでJSONへ変換できる型にデータを整える(RubyではHash型になる)
- Serialization: そのデータをJSONに変換する
それぞれどのような外部ライブラリがあり,パフォーマンスや注意点は何かを簡単に調べた.
検証した
パフォーマンスは Benchmark
モジュールを利用した.
また,データはAuthorというモデルに適当に100個程度のレコードを作成してタネとした.もちろんお手持ちのデータなんでも良い.
なお実行はCPUを4GB割り当てたDockerコンテナで行っているため,実行時間に関する数字は相対的な比較に用いるものと思って欲しい.
Data Preparation
検討したのはこの6つ
- JBuilder
- Active Model Serializer
- FastJSON API
- JSON API Serializer
- JSON API Resources
- RABL
JBuilder
- 無知だった僕はてっきりこれで実装しようかと考えていた
- Documentは比較的例が多くて見やすい
- 記述方法にクセがあり,partialを用いてJSON書こうと思うとかなり面倒臭い
- パフォーマンスは悪い
# app/views/author.json.jbuilder
json.id author.id
json.name author.name
# rails console
renderer = ApplicationController.new
Benchmark.measure do
100.times do
Author.all.each do |author|
renderer.render_to_string('/author', locals: {author: author})
end
end
end
@real=1999.7994155000197
さすがにこれは悪すぎる.render部分にも時間がかかっているため,比較できないと言われればそれまでだが,明らかに遅かった.
ActiveModelSerializer
- Qiitaの記事はこれについてが最も多いように思える
- パフォーマンスは悪くはない
# Gemfile
gem 'active_model_serializers'
# app/serializers/author_serializer.rb
class AuthorSerializer < ActiveModel::Serializer
has_many :posts
attributes :id, :name
end
# `rails g serializer author` で生成される
# rails console
Benchmark.measure do
100.times do
Author.all.each do |author|
puts AuthorSerializer.new(author).as_json
end
end
end
@real=2.2909035999909975
Fast JSON
- Netflixが開発したデータオブジェクト生成gem.
- JSON:APIのフォームで記述される(RESTとGraphQLとの中間的な構造だという考えもある)
- POSTでもGETでも,データ記述に制約があるため,クライアント側との相談が必要
- とはいうものの,性能は良い([Netflix Tec Blog | 和訳])
# Gemfile
gem 'fast_jsonapi'
# app/serializers/author_serializer.rb
class AuthorSerializer
include FastJsonapi::ObjectSerializer
attributes :name
has_many :posts
end
# rails console
Benchmark.measure do
100.times do
Author.all.each do |author|
puts AuthorSerializer.new(author).serializable_hash
end
end
end
@real=1.669708500005072
たしかに早い
JSON API Resource
- まず公式ドキュメントが最新になっていない(きれいなDocがあるが)
- なのでhelloworldがそもそも動かない
- イシューも上がっているのに一向に返事がない
- というわけで諦める
# Gemfile
gem 'jsonapi-resources'
# app/serializers/author_resource.rb
class AuthorResource < JSONAPI::Resource
has_many :posts
attributes :name
end
# rails console
Benchmark.measure do
100.times do
Author.all.each do |author|
JSONAPI::ResourceSerializer.new(AuthorResource).serialize_to_hash(AuthorResource.new(author, nil))
end
end
end
# doesn't work
JSON API Rails
gem 'jsonapi-rails
が環境変数に対してエラーを起こしてbundle installできなかったのでスキップする
RABL
# Gemfile
gem 'rabl'
# app/views/author.rabl
object @job
attributes :id, :name
# rails console
Benchmark.measure do
100.times do
Author.all.each do |author|
Rabl.render(author, 'author', :view_path => 'app/views', :format => :hash)
end
end
end
@real=59.919160100020235
使う理由が思いつかない
結果
FastJSON > AMS >> RABL >> JBuilder | JSON API Rails | JSON API Resources
JSON Serialization
検討したのはこの3つ
- Oj
- JSON
- Yajl
パフォーマンスの計測
これらはHashをJSONに変換するだけなのでパフォーマンスを比較する方法が比較的簡易
# Gemfile
gem 'oj'
gem 'yajl-rails'
gem 'json' #
これらをbundle install
してRailsコンソールを開く.
最初に yajl
をrequireしないとエラーになるので
# rails console
> require 'yajl'
> hash = {
"id": 0,
"post": {
"id": 0,
"is_closed": true
},
"author": {
"id": 0,
"name": "string",
"company": {
"id": 0,
"name": "string"
},
"books": [
{
"id": 0,
"name": "string"
}
]
},
"book": {
"id": 0,
"name": "string",
"author": {
"id": 0,
"name": "string"
}
},
"phases": [
{
"id": 0,
"name": "string",
"date": "2020-01-21T11:35:13.699Z",
"order": 0,
"is_completed": true
}
],
"fee": 0,
"publish_date": "2020-01-21",
"payment_date": "2020-01-21"
}
# 実際にはあと10行分のkey:valueを持っていたがここでは省略する
> Benchmark.bm do |x|
x.report { 100.times { Oj.dump(hash) } }
x.report { 100.times { JSON.generate(hash) } }
x.report { 100.times { Yajl::Encoder.encode(hash) } }
end
####結果
@real=0.002964600018458441 # Oj
@real=0.005538500001421198 # JSON
@real=0.0038283999892883003 # yajl
ということでOjが一番早かった
*一応他の文献も調べると,他でもOjが最も早いと記述
なおFastJson APIはOjをデフォルトで利用している
実装の留意点
上記の通り, FastJSON + Ojという組み合わせが最もパフォーマンスに優れている.
パフォーマンスに秀でているFastJSON APIだが,留意点としてはJSON:APIという記述の方法でJSONga吐き出されるため,フロントエンドでの受け取ったデータの扱い方が多少煩雑になるということである.フロントエンドエンジニアと分けて開発している場合はしっかり事前に相談しよう.
Ojを用いる時には,少しだけ特殊な挙動があるので注意が必要.このブログに詳細が書かれているが,要は":hoge"
のようにストリングにコロンが入っているとシンボルに変換されてしまうというもの
https://qiita.com/ts-3156/items/b02d32dd625273d2e342
と書かせてもらいましたが,結局のところは
そもそもNがいくつくらいのデータ量をJSONでやりとりしようとしているのか考えて,
パフォーマンスを優先するのが望まれるのか,フロントエンドの柔軟性を重視するのかエンジニアチームで相談するのがいいのだと思います.
参考リンク
[ 1 ] JSON Serialization in Rails: A Complete Guide (Jun 15, 2018)
何はともあれこれを見て欲しい.
https://buttercms.com/blog/json-serialization-in-rails-a-complete-guide
[ 2 ] Fast JSON API serialization with Ruby on Rails (Feb 1, 2018) | Netflix Technology Blog
NetflixがFastJSONの優位性を主張するブログ.再現用のソースコードもある
https://netflixtechblog.com/fast-json-api-serialization-with-ruby-on-rails-7c06578ad17f
(なんか和訳した人がいる:https://qiita.com/rana_kualu/items/700284059cb0d893dc0e)
[ 3 ] Fast JSONAPI vs AMS with OJ (Feb 2, 2018)
FastJSONのJSON:APIフォーマットが嫌ならAMS+Ojがいいと思ったが当然すでに調べている人がいる
https://medium.com/@yukikawamoto/fast-jsonapi-vs-ams-with-oj-ef9e10997ebd
[ 4 ] Fast JSON API をつかって得た、JSON:APIの知見 (Dec 21, 2019)
じゃあFastJSONを現実に使う時にフォーマットが面倒だなということに触れている記事
https://qiita.com/yoshixj/items/6499490f6fbefea05cae
[ 5 ] RailsデフォルトのJSONレンダリングは遅いので注意。Ruby/RailsでJSON生成時に最もパフォーマンスが良い方法を調べてみた。(Jan 30, 2019)
やっぱりOjは性能よし
https://qiita.com/ttiger55/items/d144b8094d61b70955bf