ActiveScaffold の性能をみてみました

ここ最近、各アプリケーションサーバにおける性能比をみるために、render :text => 'xxx' ばかり動かしてました。
で、ちょっと数字がですぎている気がしたので、もうちょっと複雑なアプリケーションでのスループット感覚をつかんでおきたくてやってみました。


やったこと

  1. ActiveScaffold を利用して単純なマスタメンテ画面を作成して、Apache Bench で性能計測
  2. 静的ファイル化して、public 以下に配置して性能計測
  3. 静的ファイル化して、app/views 以下に配置して性能計測
  4. ふつうの scaffold で単純なマスタメンテ画面を作成して性能計測
  5. will_paginate で表示データサイズを絞って (ActiveScaffold のときと同じにして) 性能計測

結果

上記番号 Mongrel での RPS Thin での RPS
1 12.91 13.89
2 225.01 1254.29
3 87.16 137.80
4 27.28 28.87
5 47.18 54.26

# RPS = Requests per second [#/sec]
# 1回ずつしか測ってないので、おかしな数字がはいってるかもしれません。 <(_ _)>


なんだかとっても遅いのですが、こんなものでしょうかね? (^^;
# PostgreSQLRails も ab も同一筐体ですが、CPU は振りきれてなかったです...


環境

  • 処理系、ライブラリのバージョンは以下のとおり
% ruby186 -v
ruby 1.8.6 (2008-08-11 patchlevel 287) [i686-darwin9.6.0]

% pg_ctl --version
pg_ctl (PostgreSQL) 8.3.5

% ab -V
This is ApacheBench, Version 2.3 <$Revision: 655654 $>

% rails -v
Rails 2.2.2

% gem list --local

*** LOCAL GEMS ***

mongrel (1.1.5)
postgres-pr (0.5.1)
rails (2.2.2)
thin (1.0.0)
will_paginate (2.2.2)
...

# 目ぼしいものを適当に抜粋

作業ログ

1. ActiveScaffold を利用したアプリで性能測定

  • いつものように..
% rails activescaffold --database=postgresql
% cd activescaffold
% ruby186 script/plugin install git://github.com/activescaffold/active_scaffold.git -r rails-2.2
Initialized empty Git repository in .git/
warning: no common commits
remote: Counting objects: 207, done.
remote: Compressing objects: 100% (193/193), done.
remote: Total 207 (delta 25), reused 95 (delta 9)
Receiving objects: 100% (207/207), 136.30 KiB, done.
Resolving deltas: 100% (25/25), done.
  • データベースの準備
% sudo su postgres -c '/usr/local/pgsql/bin/createdb activescaffold_production'
% sudo su postgres -c '/usr/local/pgsql/bin/createuser activescaffold'
Shall the new role be a superuser? (y/n) y
  • model を作成して migrate

(その前に database.yml をちょこちょこっと修正...)

% ruby186 script/generate model person name:string address:text note:text
% rake db:migrate RAILS_ENV=production
  • controller を作成
% ruby186 script/generate controller Addressbooks

% vi app/controllers/addressbooks_controller.rb
class AddressbooksController < ApplicationController
    active_scaffold :customer                  ## <=== 追加
end
  • ActiveScaffold 用の layout を作成
% vi app/views/layouts/addressbooks.html.erb

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  <head>
    <meta http-equiv="content-type" content="text/html;charset=UTF-8" />
    <title>Todos: <%= controller.action_name %></title>
    <%= javascript_include_tag :defaults %>    ## <=== 重要
    <%= active_scaffold_includes %>            ## <=== 重要
  </head>
  <body>

    <p style="color: green"><%= flash[:notice] %></p>

    <%= yield  %>

  </body>
</html>
  • データの作成

script/server のときとちがって、-e オプション付かないことに注意!
このあたりは masuidrive さんの昔の記事を参考にしつつ...

% ruby186 script/console production
Loading production environment (Rails 2.2.2)
>> Person.find(:all)
=> []

>> (1..40).each do |index|
?>     Person.create :name => "Test#{index}", :address => "Address#{index}"*10, :note => "Note#{index}"*10
>>   end
=> 1..40

  • 動作確認

partial がやたらあることや、SQL を確認しておきます

% ruby186 script/server
=> Booting Mongrel (use 'script/server webrick' to force WEBrick)
=> Rails 2.2.2 application starting on http://0.0.0.0:3000
=> Call with -d to detach
=> Ctrl-C to shutdown server
** Starting Mongrel listening at 0.0.0.0:3000
** Starting Rails with development environment...
** Rails loaded.
** Loading any Rails specific GemPlugins
** Signals ready.  TERM => stop.  USR2 => restart.  INT => stop (no restart).
** Rails signals registered.  HUP => reload (without restart).  It might not work well.
** Mongrel 1.1.5 available at 0.0.0.0:3000
** Use CTRL-C to stop.
  SQL (0.2ms)   SET client_min_messages TO 'panic'
  SQL (0.2ms)   SET client_min_messages TO 'notice'
  SQL (0.2ms)   SET client_encoding TO 'unicode'


Processing AddressbooksController#index (for 127.0.0.1 at 2009-02-03 16:32:29) [GET]
  SQL (1.0ms)   SELECT count(*) AS count_all FROM "people"
  Person Load (2.8ms)   SELECT "people".* FROM "people" ORDER BY people."created_at" ASC LIMIT 15 OFFSET 15
Rendering template within layouts/addressbooks
Rendering addressbooks/list
Rendered _list_header (1.5ms)
Rendered _list_column_headings (4.1ms)
Rendered _messages (0.2ms)
Rendered _list_actions (1.8ms)
Rendered _list_record (30.8ms)
Rendered _list_actions (1.1ms)
Rendered _list_record (3.8ms)
Rendered _list_actions (1.0ms)
Rendered _list_record (3.5ms)
Rendered _list_actions (1.0ms)
Rendered _list_record (3.4ms)
Rendered _list_actions (1.0ms)
Rendered _list_record (3.4ms)
Rendered _list_actions (1.0ms)
Rendered _list_record (3.4ms)
Rendered _list_actions (1.2ms)
Rendered _list_record (3.5ms)
Rendered _list_actions (1.0ms)
Rendered _list_record (3.4ms)
Rendered _list_actions (1.0ms)
Rendered _list_record (3.4ms)
Rendered _list_actions (1.0ms)
Rendered _list_record (3.4ms)
Rendered _list_actions (1.0ms)
Rendered _list_record (3.4ms)
Rendered _list_actions (1.0ms)
Rendered _list_record (3.3ms)
Rendered _list_actions (1.0ms)
Rendered _list_record (3.4ms)
Rendered _list_actions (1.0ms)
Rendered _list_record (3.3ms)
Rendered _list_actions (1.0ms)
Rendered _list_record (3.4ms)
Rendered _list_pagination_links (2.3ms)
Rendered _list (95.1ms)
Completed in 123ms (View: 107, DB: 4) | 200 OK [http://127.0.0.1/addressbooks]
  • production 環境で起動
% ruby186 script/server -e production
=> Booting Mongrel (use 'script/server webrick' to force WEBrick)
=> Rails 2.2.2 application starting on http://0.0.0.0:3000
=> Call with -d to detach
=> Ctrl-C to shutdown server
** Starting Mongrel listening at 0.0.0.0:3000
** Starting Rails with production environment...
** Rails loaded.
** Loading any Rails specific GemPlugins
** Signals ready.  TERM => stop.  USR2 => restart.  INT => stop (no restart).
** Rails signals registered.  HUP => reload (without restart).  It might not work well.
** Mongrel 1.1.5 available at 0.0.0.0:3000
** Use CTRL-C to stop.


Processing AddressbooksController#index (for xx.xx.xx.xx at 2009-02-03 14:21:27) [GET]
Rendering template within layouts/addressbooks
Rendering addressbooks/list
Completed in 81ms (View: 66, DB: 6) | 200 OK [http://xx.xx.xx.xx/addressbooks]
  • 測定
% ab -c 10 -n 1000 http://127.0.0.1:3000/addressbooks
This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

(snip)

Requests per second:    12.91 [#/sec] (mean)

(snip)

遅い... orz
# 以降は Requests per seconds の部分だけ表示することにします


  • Thin にも載せて測定
% ab -c 10 -n 1000 http://127.0.0.1:3000/addressbooks
Requests per second:    13.89 [#/sec] (mean)

2. public 以下に静的ファイルをおいて測定

  • 静的ファイルの取得
% wget http://127.0.0.1:3000/addressbooks/list --output-document=public/static_list.html
% ab -c 10 -n 10000 http://127.0.0.1:3000/static_list.html
Requests per second:    225.01 [#/sec] (mean)
  • Thin で測定
% ab -c 10 -n 10000 http://127.0.0.1:3000/static_list.html
Requests per second:    1254.29 [#/sec] (mean)


Mongrel と Thin の差が気になります...??



3. app/views 以下に静的ファイルをおいて測定
(これで、フレームワークを通すことによるオーバーヘッドがわかるはずです)

% mv public/static_list.html app/views/addressbooks
% ab -c 10 -n 10000 http://127.0.0.1:3000/addressbooks/static_list
Requests per second:    87.16 [#/sec] (mean)
  • Thin で測定
% ab -c 10 -n 10000 http://127.0.0.1:3000/addressbooks/static_list
Requests per second:    137.80 [#/sec] (mean)


静的ファイルにする前と比べると、render 処理にかなり時間がとられてるのがこの結果からもわかりますね。



4. ふつうの scaffold を利用したアプリで性能測定

  • いつものように..
% rails normalscaffold --database=postgresql
% cd normalscaffold

% ruby186 script/generate scaffold person name:string address:text note:text
  • 動作確認

database.yml の接続先を修正しておいて、先のデータを利用します

% ruby186 script/server
=> Booting Mongrel (use 'script/server webrick' to force WEBrick)
=> Rails 2.2.2 application starting on http://0.0.0.0:3000
=> Call with -d to detach
=> Ctrl-C to shutdown server
** Starting Mongrel listening at 0.0.0.0:3000
** Starting Rails with development environment...
** Rails loaded.
** Loading any Rails specific GemPlugins
** Signals ready.  TERM => stop.  USR2 => restart.  INT => stop (no restart).
** Rails signals registered.  HUP => reload (without restart).  It might not work well.
** Mongrel 1.1.5 available at 0.0.0.0:3000
** Use CTRL-C to stop.


Processing PeopleController#index (for 127.0.0.1 at 2009-02-03 15:52:10) [GET]
  SQL (0.3ms)   SET client_min_messages TO 'panic'
  SQL (0.2ms)   SET client_min_messages TO 'notice'
  SQL (0.2ms)   SET client_encoding TO 'unicode'
  Person Load (5.9ms)   SELECT * FROM "people"
Rendering template within layouts/people
Rendering people/index
Completed in 46ms (View: 35, DB: 7) | 200 OK [http://127.0.0.1/people]

(もちろん production 環境で動かしてます)

% ab -c 1 -n 1000 http://127.0.0.1:3000/people
Requests per second:    27.28 [#/sec] (mean)
  • Thin で測定
% ab -c 1 -n 1000 http://127.0.0.1:3000/people
Requests per second:    28.87 [#/sec] (mean)

ちなみに、ページングされてないので、ActiveScaffold のときより 3倍近いデータをデータベースから取得して表示しています
ちょっと不公平でしょうか...



5. ふつうの scaffold + ページングで測定

  • will_paginate のインストール

最新版は github に移行しrails での使い方もちょびっと変わったらしい

% gem install will_paginate

% vi config/environment.rb

(snip)
...
require 'will_paginate'           ## <=== 追加
  • controller の修正
% vi app/controllers/people_controller.rb

class PeopleController < ApplicationController
# GET /people
# GET /people.xml
def index
  # @people = Person.find(:all)
  @people = Person.paginate(:page => params[:page], :per_page => 15)  ## <=== 修正

  (snip)
  • view の修正
% vi app/views/people/index.html.erb

<h1>Listing people</h1>

<table>
  <tr>
    <th>Name</th>
    <th>Address</th>
    <th>Note</th>
  </tr>

  <% for person in @people %>
  <tr>
    <td><%=h person.name %></td>
    <td><%=h person.address %></td>
    <td><%=h person.note %></td>
    <td><%=h person.created_at %></td>     ## <=== 追加
    <td><%= link_to 'Show', person %></td>
    <td><%= link_to 'Edit', edit_person_path(person) %></td>
    <td><%= link_to 'Destroy', person, :confirm => 'Are you sure?', :method => :delete %></td>
  </tr>
  <% end %>
</table>

<br />                                     ## <=== 追加
<%= will_paginate(@people) %>              ## <=== 追加

<br />

<%= link_to 'New person', new_person_path %>
  • 動作確認
% ruby186 script/server
=> Booting Mongrel (use 'script/server webrick' to force WEBrick)
=> Rails 2.2.2 application starting on http://0.0.0.0:3000
=> Call with -d to detach
=> Ctrl-C to shutdown server
** Starting Mongrel listening at 0.0.0.0:3000
** Starting Rails with development environment...
** Rails loaded.
** Loading any Rails specific GemPlugins
** Signals ready.  TERM => stop.  USR2 => restart.  INT => stop (no restart).
** Rails signals registered.  HUP => reload (without restart).  It might not work well.
** Mongrel 1.1.5 available at 0.0.0.0:3000
** Use CTRL-C to stop.


Processing PeopleController#index (for 127.0.0.1 at 2009-02-03 16:26:22) [GET]
  SQL (0.3ms)   SET client_min_messages TO 'panic'
  SQL (0.2ms)   SET client_min_messages TO 'notice'
  SQL (0.2ms)   SET client_encoding TO 'unicode'
  Person Load (3.5ms)   SELECT * FROM "people" LIMIT 15 OFFSET 0
SQL (0.7ms)   SELECT count(*) AS count_all FROM "people"
  Rendering template within layouts/people
  Rendering people/index
Completed in 52ms (View: 34, DB: 5) | 200 OK [http://127.0.0.1/people]


:condition の設定をしてないので Order by は違いますが、LIMIT は ActiveScaffold のときと同じになっています

% ab -c 10 -n 1000 http://127.0.0.1:3000/people
Requests per second:    47.18 [#/sec] (mean)
  • Thin で測定
% ab -c 10 -n 1000 http://127.0.0.1:3000/people
Requests per second:    54.26 [#/sec] (mean)

ActiveScaffold のときより 4倍くらいはよいですね...