昨日 Ginza.rb 第23回 APIのみのRailsアプリ「Rails API」を読もう! で Ginza.rb に初参加してきたので復習メモ。
ちなみに Ginza.rb は二周年だそうですよ。おめでとうございます!
Ginza.rb の内容は Rails5 でとりこまれることが決まって目下議論中らしい Rails API についてのコードリーディングでした。
まだ Rails 本体にマージされていないので、普通の rails と rails-api で比較しながら使ってみました。
アプリ作成
$ gem install rails-api
$ rails new blog_app # 普通の rails
$ rails-api new blog_api # API な rails
middleware 比較
Rails API は使用している middleware を単に減らしているだけぽかった。比較してみる。
blog_app$ ./bin/rake middleware > app.middleware
blog_api$ ./bin/rake middleware > api.middleware
1d0
< use Rack::Sendfile
4c3
< use #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x007fa99ccebcb8>
---
> use #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x007f5618cc2200>
6d4
< use Rack::MethodOverride
10d7
< use WebConsole::Middleware
18,20d14
< use ActionDispatch::Cookies
< use ActionDispatch::Session::CookieStore
< use ActionDispatch::Flash
25c19
< run BlogApp::Application.routes
---
> run BlogApi::Application.routes
Rails API 側で追加の middleware はなさそう。
Scaffold 追加
Entry という title, body を持つモデルを追加してみる。
blog_app$ ./bin/rails g scaffold entry title:string body:text
- snip 5 lines -
create test/fixtures/entries.yml
invoke resource_route
route resources :entries
invoke scaffold_controller
- snip 23 lines -
blog_api$ ./bin/rails g scaffold entry title:string body:text
- snip 5 lines -
create test/fixtures/entries.yml
invoke api_resource_route
route resources :entries, except: [:new, :edit]
invoke scaffold_controller
- snip 3 lines -
コードリーディング通り view(erb, jbuilder), helper, assets なんかが生成されず api_resource_route
が invoke されて :new
, :edit
が生成されていないことが確認できた。
GET
Entry を一個追加して GET してみる。
blog_app$ ./bin/rails s -p 3000
$ curl http://localhost:3000/entries.json
[{"id":1,"title":"title","body":"body","url":"http://localhost:3000/entries/1.json"}]
blog_api$ ./bin/rails s -p 3001
$ curl http://localhost:3001/entries
[{"id":1,"title":"title","body":"body","created_at":"2015-05-XXTXX:XX:XX.XXXZ","updated_at":"2015-05-XXTXX:XX:XX.XXZ"}]
Rails APP はタイムスタンプがなく url というのが追加されてた。
ちなみにそれぞれのログが
# blog_app
Started GET "/entries.json" for 127.0.0.1 at 2015-05-XX XX:XX:XX +0900
Processing by EntriesController#index as JSON
Entry Load (0.2ms) SELECT "entries".* FROM "entries"
Rendered entries/index.json.jbuilder (1.3ms)
Completed 200 OK in 3ms (Views: 2.7ms | ActiveRecord: 0.2ms)
# blog_api
Started GET "/entries" for 127.0.0.1 at 2015-05-XX XX:XX:XX +0900
Processing by EntriesController#index as */*
Entry Load (0.2ms) SELECT "entries".* FROM "entries"
Completed 200 OK in 1ms (Views: 1.0ms | ActiveRecord: 0.2ms)
となっていて Rails API は jbuilder で render してないことが確認できた。その分速い。
ベンチマーク
$ ab -n 1000 -c 10 http://localhost:3000/entries.json
$ ab -n 1000 -c 10 http://localhost:3001/entries
Rails APP | Rails API | |
---|---|---|
Server Software | WEBrick/1.3.1 | WEBrick/1.3.1 |
Server Hostname | localhost | localhost |
Server Port | 3000 | 3001 |
Document Path | /entries.json | /entries |
Document Length(bytes) | 85 | 120 |
Concurrency Level | 10 | 10 |
Time taken for tests(seconds) | 10.160 | 7.085 |
Complete requests | 1000 | 1000 |
Failed requests | 0 | 0 |
Total transferred(bytes) | 539000 | 575000 |
HTML transferred(bytes) | 85000 | 120000 |
Requests per second([#/sec] (mean)) | 98.42 | 141.14 |
Time per request([ms] (mean)) | 101.604 | 70.851 |
Time per request([ms] (mean, across all concurrent requests)) | 10.160 | 7.085 |
Transfer rate([Kbytes/sec] received) | 51.81 | 79.25 |
Requests per second で比較すると Rails API の方が 1.43 倍くらい速いことが確認できた。
Rails APP で jbuilder 使わない
APP 側で jbuilder での render を止めてみて再度ベンチマークとってみる。
def index
@entries = Entry.all
respond_to do |format|
format.html
format.json { render json: @entries.to_json, status: 200 and return }
end
end
curl してみる。
$ curl http://localhost:3000/entries.json
[{"id":1,"title":"title","body":"body","created_at":"2015-05-XXTXX:XX:XX.XXZ","updated_at":"2015-05-XXTXX:XX:XX.XXXZ"}]
url がなくなって Rails API の結果と要素が同じになった。
ログを確認してみる。
Started GET "/entries.json" for 127.0.0.1 at 2015-05-XX XX:XX:XX +0900
Processing by EntriesController#index as JSON
Entry Load (0.2ms) SELECT "entries".* FROM "entries"
Completed 200 OK in 2ms (Views: 0.1ms | ActiveRecord: 0.2ms)
jbuilder の render がなくなったのが確認できたので、再度ベンチマークとってみた。
Rails APP w/o jbuilder | Rails API | |
---|---|---|
Server Software | WEBrick/1.3.1 | WEBrick/1.3.1 |
Server Hostname | localhost | localhost |
Server Port | 3000 | 3001 |
Document Path | /entries.json | /entries |
Document Length(bytes) | 120 | 120 |
Concurrency Level | 10 | 10 |
Time taken for tests(seconds) | 8.389 | 7.085 |
Complete requests | 1000 | 1000 |
Failed requests | 0 | 0 |
Total transferred(bytes) | 575000 | 575000 |
HTML transferred(bytes) | 120000 | 120000 |
Requests per second([#/sec] (mean)) | 119.20 | 141.14 |
Time per request([ms] (mean)) | 83.894 | 70.851 |
Time per request([ms] (mean, across all concurrent requests)) | 8.389 | 7.085 |
Transfer rate([Kbytes/sec] received) | 66.93 | 79.25 |
ちょっと速くなった。
結論
「初参加の方から一言」通り、やっぱり
RailsAPI使わないかもしれない…
というよりは Rails APP w/o jbuilder で十分な気がしなくもない。
pull request の中でシリアライズに active_model_serializers をデフォルトで使用するみたいなことが議論されている?ので本体に正式にマージされたら、あらためてベンチマークをとってみたいと思います。
適当にでっちあげた投稿(次回の Ginza.rb のネタ?)なので、間違いとかあれば指摘していただけると助かります。