APIフレームワークGrapeをRuby on Railsの中で動かすと遅いぞ

ある仕事でスマフォ用のAPIサーバーを作る事になり、REST-like APIが簡単に作れるフレームワーク grape を調査してみました。grapeの良さは、DSLで簡単にAPIサーバーが書ける点とRackで動く軽いフレームワークなのでRuby on Railsに比べ高いパフォーマンスが期待できる点です。

システム構成

Grapeは Mounting に書かれているようにいくつかの構成で動かせます

今回のシステムでは管理者用の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)) }
  • 性能比較のために app/views/todos/index.json.jbuilder を変更
json.array!(@todos) do |todo|
  json.extract! todo, :id, :due, :task, :created_at, :updated_at
end

Grape

grape ページの情報を基に API を作成

  • 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の中で動かした方が開発しやすいかも知れませんね。