APIフレームワークGrapeをRuby on Railsの中で動かすと遅いぞ
ある仕事でスマフォ用のAPIサーバーを作る事になり、REST-like APIが簡単に作れるフレームワーク grape を調査してみました。grapeの良さは、DSLで簡単にAPIサーバーが書ける点とRackで動く軽いフレームワークなのでRuby on Railsに比べ高いパフォーマンスが期待できる点です。
システム構成
Grapeは Mounting に書かれているようにいくつかの構成で動かせます
- Rack: Rack上で動かす
- ActiveRecord without Rails: Rackの上で動かすけど ActiveRecord を使う
- Rails: Ruby on Railsの中で動かす
- ...
今回のシステムでは管理者用のWebアプリは Ruby on Railsで作るので、モデルを共有できるRuby on Railsに組み込み使うのが魅力的です。
評価用コードを作ってみた
準備
- まずはRailsのプロジェクトを作り、scaffoldでいつものアプリを作成
$ rails new api_test
$ cd apt_test
$ rails g scaffold todo due:date task:string
- テストデータ作成 db/seed.rb も作成
Todo.delete_all 100.times { |i| Todo.create!(due: Time.now + i.day, task: sprintf("task%02d", i)) }
json.array!(@todos) do |todo| json.extract! todo, :id, :due, :task, :created_at, :updated_at end
Grape
- Gemfile
source 'https://rubygems.org' .... gem 'grape'
- API のコード
class SimpleApi < Grape::API version 'v1' format :json resource :todos do desc "Return all todos." get do Todo.all end end end
- config/routes.rb に APIをマウント
Rails.application.routes.draw do resources :todos mount SimpleApi => '/api' end
- config/application.rb にAPIのコードを読み込むように設定
module ApiTest class Application < Rails::Application .... config.paths.add File.join('app', 'api'), glob: File.join('**', '*.rb') config.autoload_paths += Dir[Rails.root.join('app', 'api', '*')] end end
これで http://localhost:3000/api/v1/todos をアクセスするとtodosテーブルの全内容がJSONで取得できます
性能が気になる
Ruby on Railsのコードも http://localhost:3000/todos.json で JSONを返せるます。grapeがどれくらい性能が良いのかを比較してみました。
比較する環境は実際の環境に近くなるように
- RAILS_ENV は production
- サーバーは unicorn、worker_processes は 4
- アプリは EC2 t2.micro で実行
- ただし、RDBはsqlite3のまま、Nginx等のフロントエンドは無し
ab -c 8 -n 1000 URL で性能を計測
Framework | Requests per second |
---|---|
Grape within Rails | 33.66 |
Ruby on Rails | 34.49 |
Grape の性能は Ruby on Rails と同じくらい!? もちろん、テストデータ、abのパラメータにより多少状況は変わりますが・・・
ActiveRecord without Railsを試した
Rackの上で直接動くシンプルな grape の性能がこんなに低いのは納得出来なかったので、ActiveRecord without Rails で試してみました
- Rackの上で動かす grape.ru を作成
require 'grape' require 'active_record' require 'sqlite3' require_relative 'app/api/simple_api' require_relative 'app/models/todo' use ActiveRecord::ConnectionAdapters::ConnectionManagement ActiveRecord::Base.configurations = YAML.load_file('config/database.yml') ActiveRecord::Base.establish_connection(:production) run SimpleApi
この grape.ru を指定し unicorn 起動
Framework | Requests per second |
---|---|
Grape | 56.65 |
Ruby on Rails に比べ 1.6倍の性能が出ました!
結論
テスト中のサーバーのメモリー使用量も調べてみました。実メモリ使用量(RES)は Ruby on Railsに比べると半分以下です。
Framework | Requests per second | VIRT(memory) | RES(memory) |
---|---|---|---|
Grape within Rails | 33.66 | 345308 | 91828 |
Ruby on Rails | 34.49 | 345308 | 91828 |
Grape | 56.65 | 253472 | 38852 |
結論としては、grape を使って API サーバーを作るなら Rackベースで起動し、 Ruby on Railsとは別に動かした方が良い。
ただし、開発時は Ruby on Railsの中で動かした方が開発しやすいかも知れませんね。