• 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

正方行列 が冪零であるとは、ある整数 に対して (零行列)が成り立つことをいう。 が同じ型の冪零行列で を満たすとき、次を示せ。

(1)  は冪零

(2)  は冪零

posted by Face lvs on Thu 26 Dec 2013 at 20:18

 Passenger + Nginx でアプリケーションを起動しようとしたときに、下記のようなエラーが出ました。

   1  git://github.com/nzifnab/msgpack-rails.git (at master) is not checked out. Please run `bundle install` (Bundler::GitError)

 もちろん bundle install は実行済みです。msgpack-rails を使用するために、Gemfile に下記のような記述をしてあるのですが、色々調べたところ、Passenger が git からインストールした gem を認識してくれないようです。

   1  gem 'msgpack-rails', :git => 'git://github.com/nzifnab/msgpack-rails.git'

 bundle install --deployment で解決するという情報も見つけたのですが、これは vendor ディレクトリにgemを持ってくるため結構な時間がかかります。デプロイでは世代管理していて、デプロイ時に新しいディレクトリを作成してデプロイしているので、開発環境のサーバへデプロイするたびに時間がかかるのはちょっと困るので他の方法を探したところ、bundle package でOKという情報を見つけました。これはgemのキャッシュを vendor ディレクトリに持つもので、すぐに終わります。ただオプションで --all を指定しないと git 経由のgemをキャッシュしてくれないようです。

   1  bundle package --all

とすることで、git からの gem も見つけてくれるようになりました。

(参考サイト)deploying rails3 apps with bundler and phusion passenger: .bundle dir not found

posted by Png akanuma on Wed 25 Dec 2013 at 06:01

 今まではネイティブのクライアントへJsonでレスポンスを返していたのですが、新しいサービスを開発する際に、レスポンスをMessagePackにしてはどうかとクライアントサイドから提案があったので検証してみました。

 ちなみにリクエストまでMessagePackにすると、リクエスト内容が簡単に参照できなくなってデバッグや問い合わせ時の調査が大変ということで、今回はレスポンスのみに使う想定です。

検証用アプリ


 構成は下記の通りです。

  • Nginx
  • Passenger
  • Ruby2.0
  • Rails4

 今回の検証はJson圧縮とMessagePack圧縮の比較です。アプリはRails4で実装します。また、JsonはデフォルトのJSONクラスよりもoj(Optimized JSON)が早いということなのでそちらを使用しています。MessagePackの方はMsgpackRailsを使います。Gemfileには下記を追加します。

(参考) JSONの替わりに使ったOJが速い
(参考) MsgpackRails

   1  gem 'oj'
   2  gem 'msgpack-rails', :git => 'git://github.com/nzifnab/msgpack-rails.git'

 Controllerは下記のように実装しました。

   1  class StaticPagesController < ApplicationController
   2  
   3    require 'oj'
   4  
   5    after_action :testcompress
   6  
   7    respond_to :json, :mpac
   8  
   9    SERIALIZE_TEST_RESPONSE = {(省略)}
  10  
  11    def serialize_test
  12      respond_to do |format|
  13        format.json { render :json => SERIALIZE_TEST_RESPONSE }
  14        format.mpac { render :text => SERIALIZE_TEST_RESPONSE.to_msgpack, :content_type => 'application/x-mpac' }
  15      end
  16    end
  17  
  18    def testcompress
  19      response.body = ActiveSupport::Gzip.compress(response.body)
  20    end
  21  
  22  end

  • oj が追加されていると、MultiJSONによって自動的に oj が選択されるようです。
  • シリアライズするHashデータを SERIALIZE_TEST_RESPONSE として定義しておきます。
  • 処理内容としてはテスト用のデータをシリアライズして返すだけです。
  • また、今回はシリアライズ+圧縮を試すのですが、Rails側で圧縮した場合と、Nginxで圧縮した場合も比較してみます。Rails側で圧縮する場合の処理を testcompress メソッドに定義しておいて、after_action で gzip 処理を行います。Nginx側で圧縮する場合は after_action をコメントアウトします。

 検証方法としては、JMeterサーバを3つ用意して、100スレッドから上記APIへかけられるだけ負荷をかけてみるというやり方です。Nginxで圧縮するケースではHTTPヘッダマネージャでAccept-Encodingヘッダを設定する必要があります。また、Nginxの設定ファイルで下記の設定を有効にします。

   1  gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/x-mpac;

 デフォルトでは application/x-mpac 以外が設定された状態でコメントアウトされていますので、コメントアウトを解除して application/x-mpac を追加してあります。

検証結果


検証結果は下記の通りです。

検証結果
種別圧縮サンプル数平均中央値90%LINE最小値最大値スループットKB/sec
JsonRails48626181618052002252258158.57306.30
MessagePackRails2713723173213565533889.051701.66
JsonNginx48824178517751988272206159.11393.07
MessagePackNginx33378324924827944661093.612395.42
MessagePack非圧縮34617524624627166101134.3319178.19

 Jsonの場合はCPUは常に振り切っている状態でしたが、MessagePackではCPUに余裕がありました。
 テスト結果のスループットとしてはMessagePack非圧縮のケースが一番速かったのですが、MessagePackのNginxでの圧縮の場合と大きな差はなく、実際のケースでは転送するデータ量を少なくすることによるメリットも大きいので、今回はMessagePackのNginx圧縮パターンを採用することにしました。

posted by Png akanuma on Sat 21 Dec 2013 at 18:28

 仕事で新しいサービスのDBを何にするか検討していて、MongoDBと同じような使い方ができてサーバコストが抑えられるものがないか探していたときにCouchbaseのことを思い出してハンズオンセミナーなど受けてきました。結局Couchbaseは使わないことにしたのですが、主にMongoDBと比べてどうだったかといったあたりを書いておきます。

Couchbaseについてざっくりと


 まずCouchbaseがどういったものかをざっくり説明しておくと、MongoDBと同じようなドキュメント指向のNoSQL製品で、JSONデータを格納します。
 売りにしている特徴としては下記のような点が挙げられています。

  • 容易なスケーラビリティ
  • 安定したハイパフォーマンス
  • 24時間365日安定稼働
  • 柔軟なデータモデル

MongoDBと似ているところ


 また、下記のような思想はMongoDBと共通しています。

  • データは全部メモリに載せる
  • パフォーマンス出すにはサーバを追加する(スケールアウト)

 データが100GBあれば、masterノードのメモリの合計がそれ以上になるようにしましょう、という点では両者とも同じ思想で、サーバスペック的にもCPUよりもメモリを多く積んだサーバを必要とします。

比較して気になったところなど


 逆にMongoDBと比較して違う点で気になったところ等は下記のようなところです。

MongoDB/Couchbase比較
項目MongoDBCouchbase
レプリケーションmaster-slaveレプリケーションmaster-masterレプリケーション
ノード追加コマンドで設定変更Webの管理画面から操作可能
自動Failover台数制限なし同時に一台まで
クエリSQLライクな柔軟なクエリMapReduceのみ
ドキュメント削除時ドキュメント削除時にすべて消えるメタデータが残ってしまう
商用版と無償版違いはサポートの有無商用版がベースなのでバージョンアップなどもまず商用版から

 それぞれ簡単に説明していきます。

  • master-masterレプリケーション

    MongoDBではレプリケーションの構成はmaster-slaveになりますので、masterノードのレプリカはslaveサーバが保持します。なのでシャーディング構成をとっている場合で1レプリカセット3台で構成している場合は、1シャード増やす場合には1レプリカセット追加ということになって3ノード増やす必要があります。これに対してCouchbaseはmaster-masterレプリケーションなので、あるmasterノードのレプリカは他のmasterノードが保持します。なので1シャード追加する場合にも1ノード増やすだけでOKです。masterが他のmasterのレプリカを持つ分、1ノードあたりのデータ量はMongoDBと比べると多くなりますが、レプリカデータは普段は非Activeになっていてアクセスされないので、その分のメモリは足りていなくても運用できるとのことでした。(実際の動作検証はしてません。。)

  • 管理画面からノード追加

    MongoDBではクラスタへノードを追加する場合、コマンドラインから操作する必要がありますが、CouchbaseではWebの管理画面から操作することができます。追加後のリバランスもそのまま管理画面から実行可能で、データ量が増えてもリバランスの所要時間はあまり変わらないとのことです。また、Mongoではシャードキーを自分で設定しますが、Couchbaseでは自動で振り分けが行われるので、データの内容から置かれているサーバを特定することはできません。リバランス処理の負荷はやはり高いので、すでに負荷でいっぱいいっぱいの状態のクラスタでノード追加、リバランスを行うのはつらいものがあります。

  • 自動Failoverは一台まで

    MongoDBではレプリケーションがmaster-slave構成ということもあり、Failoverは同時に何シャードで起こっても大丈夫ですが、Couchbaseでは自動Failoverは1ノードまでなので、最初の1ノードを復旧する前にもう一台落ちた場合には、クラスタが停止します。管理画面から手動でFailoverさせることは可能です。

  • クエリはMapReduceのみ

    今回Couchbaseの採用を見送った最大の理由はこれなのですが、MongoDBではSQLライクな柔軟なクエリが使用できるのに対して、CouchbaseではクエリはすべてMapReduceで行う必要があります。前もってMapReduceの処理を定義しておき、アプリケーションなどからはどのMapReduce処理かを指定して結果を参照します。開発時の効率を考えるとすべての処理をMapReduceで書くのはつらいものがあるので今回は見送りました。来年出るバージョン3.0ではSQLライクなクエリであるN1QL(ニッケル)が実装されるそうですので、期待したいところです。

  • ドキュメントを消してもメタデータが残る

    Couchbaseではドキュメントを消した場合にもドキュメントのメタデータは消されずに残ってしまうので、容量を圧迫します。この残ったメタデータを削除する処理の実行頻度を管理画面から指定できるのですが、削除処理は負荷が高いようなので、運用時にはこういった点も考慮する必要があります。

  • 商用版ありき

    OSSには元々オープンソースとして始まったものに商用のサポートなどがつくケースが多いですが、Couchbaseは元々商用製品として始まったもののソースを公開している形になります。なのであくまでメインは商用版で、バージョンアップやパッチの公開等もまず商用版に対して行われ、Couchbaseのエンジニアの手が空いたときにOSS版がメンテナンスされることになります。現状バージョンアップについては商用版の半年遅れぐらいでOSS版が追随しているようですが、今後はさらに間隔が開いたり、機能やデータ容量の制限がかかってくることもあるかもしれません。無償で利用できるものを探している場合はこの辺りがネックになりそうです。現在の商用版の最新バージョンは2.2ですが、OSS版はまだ2.1です。次のバージョンの3.0はOSSではいつ公開されるのか。。


 今回は実際に性能の検証等を行うまでに使わないという判断をしてしまいましたが、いくつか公開されているベンチマークではCouchbaseがMongoDBなどより良い結果を出しているものもあるので、N1QLが利用できるようになったらパフォーマンス検証等行ってみたいと思います。ただ無償版の状況を考えるとなかなか難しいかもしれませんが。。

posted by Png akanuma on Mon 16 Dec 2013 at 06:21

 Ruby on Rails で普通にプロジェクトを作成すると sqlite3 が使われるようになっていますが、mysql を使用するにはプロジェクト作成時に -d オプションで mysql を指定します。

   1  $ rails new turntable_test -d mysql
   2        create  
   3        create  README.rdoc
   4        create  Rakefile
   5        create  config.ru
   6        create  .gitignore
   7        create  Gemfile
   8        create  app
   9        create  app/assets/javascripts/application.js
  10        create  app/assets/stylesheets/application.css
  11        create  app/controllers/application_controller.rb
  12        create  app/helpers/application_helper.rb
  13        create  app/views/layouts/application.html.erb
  14        create  app/assets/images/.keep
  15        create  app/mailers/.keep
  16        create  app/models/.keep
  17        create  app/controllers/concerns/.keep
  18        create  app/models/concerns/.keep
  19        create  bin
  20        create  bin/bundle
  21        create  bin/rails
  22        create  bin/rake
  23        create  config
  24        create  config/routes.rb
  25        create  config/application.rb
  26        create  config/environment.rb
  27        create  config/environments
  28        create  config/environments/development.rb
  29        create  config/environments/production.rb
  30        create  config/environments/test.rb
  31        create  config/initializers
  32        create  config/initializers/backtrace_silencers.rb
  33        create  config/initializers/filter_parameter_logging.rb
  34        create  config/initializers/inflections.rb
  35        create  config/initializers/mime_types.rb
  36        create  config/initializers/secret_token.rb
  37        create  config/initializers/session_store.rb
  38        create  config/initializers/wrap_parameters.rb
  39        create  config/locales
  40        create  config/locales/en.yml
  41        create  config/boot.rb
  42        create  config/database.yml
  43        create  db
  44        create  db/seeds.rb
  45        create  lib
  46        create  lib/tasks
  47        create  lib/tasks/.keep
  48        create  lib/assets
  49        create  lib/assets/.keep
  50        create  log
  51        create  log/.keep
  52        create  public
  53        create  public/404.html
  54        create  public/422.html
  55        create  public/500.html
  56        create  public/favicon.ico
  57        create  public/robots.txt
  58        create  test/fixtures
  59        create  test/fixtures/.keep
  60        create  test/controllers
  61        create  test/controllers/.keep
  62        create  test/mailers
  63        create  test/mailers/.keep
  64        create  test/models
  65        create  test/models/.keep
  66        create  test/helpers
  67        create  test/helpers/.keep
  68        create  test/integration
  69        create  test/integration/.keep
  70        create  test/test_helper.rb
  71        create  tmp/cache
  72        create  tmp/cache/assets
  73        create  vendor/assets/javascripts
  74        create  vendor/assets/javascripts/.keep
  75        create  vendor/assets/stylesheets
  76        create  vendor/assets/stylesheets/.keep
  77           run  bundle install
  78  Enter your password to install the bundled RubyGems to your system: 
  79  Fetching gem metadata from https://rubygems.org/...........
  80  Fetching gem metadata from https://rubygems.org/..
  81  Resolving dependencies...
  82  Using rake (10.1.0) 
  83  Installing i18n (0.6.9) 
  84  Using minitest (4.7.5) 
  85  Using multi_json (1.8.2) 
  86  Using atomic (1.1.14) 
  87  Using thread_safe (0.1.3) 
  88  Using tzinfo (0.3.38) 
  89  Using activesupport (4.0.1) 
  90  Using builder (3.1.4) 
  91  Using erubis (2.7.0) 
  92  Using rack (1.5.2) 
  93  Using rack-test (0.6.2) 
  94  Using actionpack (4.0.1) 
  95  Installing mime-types (1.25.1) 
  96  Using polyglot (0.3.3) 
  97  Using treetop (1.4.15) 
  98  Using mail (2.5.4) 
  99  Using actionmailer (4.0.1) 
 100  Using activemodel (4.0.1) 
 101  Using activerecord-deprecated_finders (1.0.3) 
 102  Using arel (4.0.1) 
 103  Using activerecord (4.0.1) 
 104  Using bundler (1.3.5) 
 105  Using coffee-script-source (1.6.3) 
 106  Using execjs (2.0.2) 
 107  Using coffee-script (2.2.0) 
 108  Using thor (0.18.1) 
 109  Using railties (4.0.1) 
 110  Using coffee-rails (4.0.1) 
 111  Using hike (1.2.3) 
 112  Installing jbuilder (1.5.3) 
 113  Using jquery-rails (3.0.4) 
 114  Using json (1.8.1) 
 115  Installing mysql2 (0.3.14) 
 116  Using tilt (1.4.1) 
 117  Installing sprockets (2.10.1) 
 118  Using sprockets-rails (2.0.1) 
 119  Using rails (4.0.1) 
 120  Using rdoc (3.12.2) 
 121  Using sass (3.2.12) 
 122  Using sass-rails (4.0.1) 
 123  Using sdoc (0.3.20) 
 124  Installing turbolinks (2.0.0) 
 125  Installing uglifier (2.3.2) 
 126  Your bundle is complete!
 127  Use `bundle show [gemname]` to see where a bundled gem is installed.

 オプションなしでプロジェクトを作成したときとの違いは、作成時の出力で下記のように示されている通り、mysqlのgemが使用されます。

   1  Installing mysql2 (0.3.14)

 また、sqlite3は使用されなくなるので、下記のようなsqlite3に関する出力はなくなります。

   1  Using sqlite3 (1.3.8)

 作成される config/database.yml の差分は下記の通りです。

   1  $ diff test_app/config/database.yml turntable_test/config/database.yml 
   2  1,2c1
   3  < # SQLite version 3.x
   4  < #   gem install sqlite3
   5  ---
   6  > # MySQL.  Versions 4.1 and 5.0 are recommended.
   7  4,5c3,10
   8  < #   Ensure the SQLite 3 gem is defined in your Gemfile
   9  < #   gem 'sqlite3'
  10  ---
  11  > # Install the MYSQL driver
  12  > #   gem install mysql2
  13  > #
  14  > # Ensure the MySQL gem is defined in your Gemfile
  15  > #   gem 'mysql2'
  16  > #
  17  > # And be sure to use new-style password hashing:
  18  > #   http://dev.mysql.com/doc/refman/5.0/en/old-client.html
  19  7,8c12,14
  20  <   adapter: sqlite3
  21  <   database: db/development.sqlite3
  22  ---
  23  >   adapter: mysql2
  24  >   encoding: utf8
  25  >   database: turntable_test_development
  26  10c16,18
  27  <   timeout: 5000
  28  ---
  29  >   username: root
  30  >   password:
  31  >   socket: /tmp/mysql.sock
  32  16,17c24,26
  33  <   adapter: sqlite3
  34  <   database: db/test.sqlite3
  35  ---
  36  >   adapter: mysql2
  37  >   encoding: utf8
  38  >   database: turntable_test_test
  39  19c28,30
  40  <   timeout: 5000
  41  ---
  42  >   username: root
  43  >   password:
  44  >   socket: /tmp/mysql.sock
  45  22,23c33,35
  46  <   adapter: sqlite3
  47  <   database: db/production.sqlite3
  48  ---
  49  >   adapter: mysql2
  50  >   encoding: utf8
  51  >   database: turntable_test_production
  52  25c37,39
  53  <   timeout: 5000
  54  ---
  55  >   username: root
  56  >   password:
  57  >   socket: /tmp/mysql.sock

 sqlite3 の設定が mysql 用の設定に変更されているのがわかります。username, password などは適宜変更します。
 binstubsを実行してから、rake でDBを作成します。

   1  $ bundle --binstubs
   2  $ bin/rake db:create

 mysqlにログインしてDBが作成されていることを確認します。

   1  mysql> show databases;
   2  +----------------------------+
   3  | Database                   |
   4  +----------------------------+
   5  | information_schema         |
   6  | mysql                      |
   7  | performance_schema         |
   8  | test                       |
   9  | turntable_test_development |
  10  | turntable_test_test        |
  11  +----------------------------+
  12  6 rows in set (0.00 sec)

 動作確認用のサンプルとしてUserModelを作成します。

   1  $ bin/rails generate model User name:string email:string
   2        invoke  active_record
   3        create    db/migrate/20131211224446_create_users.rb
   4        create    app/models/user.rb
   5        invoke    test_unit
   6        create      test/models/user_test.rb
   7        create      test/fixtures/users.yml

 migrate を実行してDBにテーブルを作成します

   1  $ bin/rake db:migrate
   2  ==  CreateUsers: migrating ====================================================
   3  -- create_table(:users)
   4     -> 0.0110s
   5  ==  CreateUsers: migrated (0.0111s) ===========================================

 下記のようにテーブルが作成されていれば成功です。

   1  mysql> desc users;
   2  +------------+--------------+------+-----+---------+----------------+
   3  | Field      | Type         | Null | Key | Default | Extra          |
   4  +------------+--------------+------+-----+---------+----------------+
   5  | id         | int(11)      | NO   | PRI | NULL    | auto_increment |
   6  | name       | varchar(255) | YES  |     | NULL    |                |
   7  | email      | varchar(255) | YES  |     | NULL    |                |
   8  | created_at | datetime     | YES  |     | NULL    |                |
   9  | updated_at | datetime     | YES  |     | NULL    |                |
  10  +------------+--------------+------+-----+---------+----------------+
  11  5 rows in set (0.00 sec)

posted by Png akanuma on Wed 11 Dec 2013 at 06:29

 私物のMacBook AirにMySQLをインストールしました。せっかくなので手順をメモしておきます。

 インストール自体はHomebrewでコマンド一発です。

   1  $ brew install mysql
   2  Warning: It appears you have MacPorts or Fink installed.
   3  Software installed with other package managers causes known problems for
   4  Homebrew. If a formula fails to build, uninstall MacPorts/Fink and try again.
   5  ==> Downloading https://downloads.sf.net/project/machomebrew/Bottles/mysql-5.6.1
   6  ######################################################################## 100.0%
   7  ==> Pouring mysql-5.6.15.mavericks.bottle.tar.gz
   8  ==> Caveats
   9  A "/etc/my.cnf" from another install may interfere with a Homebrew-built
  10  server starting up correctly.
  11  
  12  To connect:
  13      mysql -uroot
  14  
  15  To have launchd start mysql at login:
  16      ln -sfv /usr/local/opt/mysql/*.plist ~/Library/LaunchAgents
  17  Then to load mysql now:
  18      launchctl load ~/Library/LaunchAgents/homebrew.mxcl.mysql.plist
  19  Or, if you don't want/need launchctl, you can just run:
  20      mysql.server start
  21  ==> /usr/local/Cellar/mysql/5.6.15/bin/mysql_install_db --verbose --user=akanuma
  22  ==> Summary
  23   /usr/local/Cellar/mysql/5.6.15: 9410 files, 349M

 インストール後の各手順については上記のインストール時の出力に示されています。
 まずOS起動時にMySQLが起動するように設定します。

   1  $ ln -sfv /usr/local/opt/mysql/*.plist ~/Library/LaunchAgents
   2  /Users/akanuma/Library/LaunchAgents/homebrew.mxcl.mysql.plist -> /usr/local/opt/mysql/homebrew.mxcl.mysql.plist

 launchctlコマンドで起動します。

   1  $ launchctl load ~/Library/LaunchAgents/homebrew.mxcl.mysql.plist 
   2  $ ps aux | grep mysql
   3  akanuma          2558   0.0  0.0  2423468    332 s000  R+   10:54PM   0:00.00 grep mysql
   4  akanuma          2555   0.0  5.3  3081112 448512   ??  S    10:54PM   0:00.40 /usr/local/Cellar/mysql/5.6.15/bin/mysqld --basedir=/usr/local/Cellar/mysql/5.6.15 --datadir=/usr/local/var/mysql --plugin-dir=/usr/local/Cellar/mysql/5.6.15/lib/plugin --bind-address=127.0.0.1 --log-error=/usr/local/var/mysql/Hiroakis-MacBook-Air.local.err --pid-file=/usr/local/var/mysql/Hiroakis-MacBook-Air.local.pid
   5  akanuma          2467   0.0  0.0  2436436   1008   ??  S    10:54PM   0:00.02 /bin/sh /usr/local/opt/mysql/bin/mysqld_safe --bind-address=127.0.0.1

 mysqlコマンドで接続できることを確認します。

   1  $ mysql -uroot
   2  Welcome to the MySQL monitor.  Commands end with ; or \g.
   3  Your MySQL connection id is 1
   4  Server version: 5.6.15 Homebrew
   5  
   6  Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
   7  
   8  Oracle is a registered trademark of Oracle Corporation and/or its
   9  affiliates. Other names may be trademarks of their respective
  10  owners.
  11  
  12  Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

posted by Png akanuma on Tue 10 Dec 2013 at 23:15

 仕事でRailsを使うことになり、APサーバの選定にあたってPuma, Unicorn, Passenger の比較検討を行いました。方法としてはJMeterでAPサーバにデプロイしたRailsアプリケーションに対して負荷をかけられるだけかけるというやり方です。


試験環境


試験の環境としては下記の構成です。

  • Ruby2.0, Rails4
  • アプリケーションサーバ:1台(VM)
  • JMeterサーバ:3台(VM)
  • JMeterクライアント:1台(通常の作業PC)
    サーバ構成
    hostanameCPU仮想コア数(Per CPU)MemoryDisk用途
    loadtest01248192MB20GBAPサーバ
    loadtest02114096MB20GBJMeterサーバ
    loadtest03114096MB20GBJMeterサーバ
    loadtest04114096MB20GBJMeterサーバ


サーバアーキテクチャの比較


各APサーバのアーキテクチャの比較は下記の通りです。

各APサーバのアーキテクチャの比較
PumaUnicornPassenger
デプロイモデルReverse ProxyReverse ProxyNginx Module
Process/ThreadMulti Processes / Multi ThreadsMulti Processes / Single ThreadMulti Processes / Single Thread(OSS Edition)
Multi Processes / Multi Threads(Commercial Edition)
その他特徴Workerプロセスごとにスレッドを立ち上げるPull型(Workerプロセス側からキューのタスクを取得しにいく)トラフィックに応じてプロセス数を自動調整。商用Editionのみマルチスレッド利用可能


処理内容


処理内容としては、純粋にAPサーバの処理能力の比較にするため、DBアクセス等はせず、フィボナッチ数列を計算して返す処理を行います。ロジックは書きサイトを参考にしました。

Rubyでメモ化を使ってフィボナッチ数を求める

   1  class StaticPagesController < ApplicationController
   2  
   3    def fibonacci
   4      @fibonacci = calc_fibonacci(params[:n].to_i)
   5    end
   6  
   7    private
   8  
   9      def calc_fibonacci(n)
  10        if (n < 2)
  11          return n;
  12        else
  13          p2 = 0;
  14          p1 = 1;
  15          2.upto(n) { p2, p1 = p1, p2 + p1 }
  16          return p1;
  17        end 
  18      end
  19  
  20  end


JMeterシナリオ


JMeterのシナリオとしては、

  • JMeterスレッド数:100
  • Ramp up:0
  • ループ数:無限ループ
  • 継続時間:5分間

という内容です。
JMeterのシナリオファイルはこちらから取得できます。


APサーバごとの設定値による差の検証


まずは各APサーバごとに、設定値の変更による差を検証しました。


Puma

Puma検証
ワーカプロセス数スレッド数サンプル数平均値(msec)中間値(msec)90%LINE (msec)最小値(msec)最大値(msec)スループット(/sec)KB/secエラー数
1500430792090197823791292519141.68414.67多発
1008241811365571071646414799.432339.490
10032243620362601136616890805.372356.8332
64322428253655113557103157802.352347.941
3232241934366467606252504798.242335.890
1664237270373313396298546782.002288.340
1696231996382376446241748762.492231.260

  • ワーカプロセス数1のケースでは全くCPUを使い切れていませんが、他のケースではCPUを使い切った状態で動いていました。
  • スレッド数を増加させてもリソースの使用量はあまり変化しませんでしたが、プロセス数を増やすと使用量がかなり増えます。
  • メモリに余裕があってもCPUが振り切る方が早いです。


Unicorn

Unicorn検証
ワーカプロセス数サンプル数平均値(msec)中間値(msec)90%LINE (msec)最小値(msec)最大値(msec)スループット(/sec)KB/secエラー数
1429411345572505537717624711.7434.54433
886866026562137217714119322.1565.15389
32429431465325284451183552103.48304.38280
6419125444017037526115819498.541466.4648
962471913601702614982454773.582275.693
1282560503432524521320587836.002459.820
160254117347291537158825835.542458.540
192252158349302588135746833.942453.870
2242297233832755929299799697.122045.152

  • ワーカプロセス数が1の場合は1CPUしか使われない
  • ワーカプロセス数をコア数と同じ8にした場合でも、すべてのコアが使われるわけではない
  • ワーカプロセス数をコア数の4倍の64まで増やすとかなりCPUが使われるようになってきて、128まで増やすとCPUはほぼ使い切られて、メモリの使用率も高くなる。
  • ワーカプロセス数を192まで増やした時点でメモリもほぼ使い切られ、224まで増やすとメモリが足りなくなり、Swapが発生して遅くなる。


Passenger

Passenger検証
プロセス数サンプル数平均値(msec)中間値(msec)90%LINE (msec)最小値(msec)最大値(msec)スループット(/sec)KB/secエラー数
16518813761282132512248690215.15646.070
82451533603553682972441810.572433.990
162467373573533812792339815.772448.770
242411653663603912812414797.272392.300

  • プロセス数が1だと4コアしか使われていない
  • プロセス数をコア数と同じにするとどのコアもかなり使われるようになる。メモリは余裕あり。
  • プロセス数をコア数以上に増やしていってもCPU, メモリの使用率はあまり変化しない。CPUは80%まで使われることが多いが、アイドルが10%前後残っていて、使い切ることはない


各APサーバの最適値同士を比較


各APサーバの最適値同士を比較して、使用するAPサーバを決定する

総合
APサーバサンプル数平均値(msec)中間値(msec)90%LINE (msec)最小値(msec)最大値(msec)スループット(/sec)KB/sec
Puma241811365571071646414799.432339.49
Unicorn252158349302588135746833.942453.87
Passenger2467373573533812792339815.772448.77

  • Pumaはスループットが一番低いのに加えて最大値、90%LINEの遅さ、不安定さで候補から除外。
  • Passengerは安定しているが最小値が他の2つに比べて大きい。90%LINE、最大値は優秀。
  • Unicornはスループットが一番大きいのと、最小値、平均値も悪くない。最大値、90%LINEがPassengerと比 べると遅いのが懸念点。

定数スループットタイマを使って実際のアクセス数に近い負荷でUnicornとPassengerを比較
APサーバサンプル数平均値(msec)中間値(msec)90%LINE (msec)最小値(msec)最大値(msec)スループット(/sec)KB/sec
Unicorn23075121013411675.32221.50
Passenger2309211912511875.38226.67

  • UnicornとPassengerでほぼ差はなし
  • Passengerは最初の比較時に他と比べて最小値が遅いことが懸念だったが、実際の状況に近いアクセス数では 問題なく、Unicornが負荷が低い状態でもメモリリソースを消費するのに対してPassengerはリソースの消費が少ないこと から、Passengerを第一候補と考える。


検証時の問題点


検証時に発生した問題点を参考までに記載しておきます。


Passenger

  • デフォルトの状態で負荷をかけたところ、下記のようなエラーが多発してHTTPステータスコード 503が返される。

   1  [ 2013-11-20 12:21:13.0578 17419/7fbbbc9da700 Pool2/Group.h:331 ]: Request queue is full. Returning an error

リクエストのQueueがあふれたことによるもののようです。Queue のサイズのデフォルトは100なので、無制限にするために passenger_max_request_queue_size を0に設定したところ、エラーは出なくなりました。


Unicorn

  • Nginxとの連携をUnixドメインソケットで行う設定で負荷をかけたところ、下記エラーが多発。

   1  2013/11/20 11:32:35 [error] 27462#0: *622742 connect() to unix:///home/test_user/server-proto/unicorn.sock failed (11: Resource temporarily unavailable) while connecting to upstream, client: 192.168.51.234, server: loadtest01.test.com, request: "GET /fibonacci?n=1000 HTTP/1.1", upstream: "http://unix:///home/test_user/server-proto/unicorn.sock:/fibonacci?n=1000", host: "loadtest01.test.com:3090"

下記サイトなどを参考に、TCPポートによる連携に変更しました。

http://www.faultserver.com/q/answers-need-to-increase-nginx-throughput-to-an-upstream-unix-soc ket-linux-kernel-tun-398972.html

変更後に負荷をかけたところ、エラーは概ね解消しました。負荷をかけ続けていると、件数は少なくなったもののエラーが発生しました。HTTPステータスコードは 502 Bad Gateway

   1  2013/11/20 14:45:29 [error] 25979#0: *254606 upstream prematurely closed connection while reading response header from upstream, client: 192.168.51.232, server: loadtest01.nubee.com, request: "GET /fibonacci?n=1000 HTTP/1.1", upstream: "http://127.0.0.1:3070/fibonacci?n=1000", host: "loadtest01.test.com:3090"

   1  [2013-11-20 14:44:09.109250] ERROR worker=114 PID:26737 timeout (31s > 30s),
   2        killing
   3        [2013-11-20 14:44:09.119595] ERROR reaped #<Process::Status: pid 26737 SIGKILL
   4        (signal 9)> worker=114
   5        [2013-11-20 14:44:10.140398]  INFO worker=114 ready

検証のためにUnicorn側のタイムアウト設定を大幅に増やしました。30秒 → 300秒に変更。Nginxの設定にも下記を追加しました。

   1  send_timeout 300;
   2  proxy_connect_timeout 300;
   3  proxy_read_timeout 300;

これでひとまずエラーは回避できました。。スループットの検証のためにタイムアウト値を大きくしていますが、実際の環境ではタイムアウト値は小さく設定する必要があります。

posted by Png akanuma on Sat 7 Dec 2013 at 23:14

MongoDB Advent Calendar 2013 の 5日目です。

 先日、MongoDB University M101P コースのTAをさせていただく機会に恵まれました。MongoDB University というのはMongoDB社が提供している、MongoDBを使用したプログラミングについてオンラインで学習できるコースです。

スクリーンショット 2013-12-01 18.00.25.png

 TA(Teaching Assistant) というのはコース内のフォーラムで、受講している生徒からの質問等に回答するのが主な役割です。以前は英語版のコースのみだったのですが、Pythonを使用したプログラミングの学習コース(M101P)のビデオが日本語でも提供されることになり、日本語を話す受講者のために日本人のTAが必要ということになりました。MongoDB University の各コースでは修了時にアンケートをとっていて、以前私が英語版で受講したときのアンケートで、TAや翻訳等の機会があれば協力しても良いか、という質問にYesと回答していたためにオファーをいただいたようです。TAを努めたそのコースも先日終了したので、感じたことや気づいたこと等書いてみたいと思います。MongoDBの技術的な話とはちょっと違ってしまいますが、ご容赦ください。

  1. 日本語での生徒からの投稿ゼロ

     まず結果として日本語でのフォーラムへの投稿数はというと、残念ながら0でした。日々フォーラムをチェックしていると、英語でのポストはどんどん増え、活発にやりとりされていくのですが、日本語でのポストはないまま終わってしまいました。そもそもJapanese Speakerの受講者がEnglish Speakerに比べてかなり少ないということはあると思いますが、こういったところでの積極性はやはり日本人よりも欧米の方々の方が強いのかなぁと思いました。

  2. 使用しているバージョンや環境などによる違いが大きい

     M101PはPythonを使用したプログラミングについてのコースで、前提としているPythonのバージョンは2.7であることも記載されているのですが、3系を使用しているために提供されているコードが正しく動作しないというポストがかなり多く見られました。また、シンプルにローカルのPC上で動かす意外にも、OpenShift などのクラウド環境で動かそうとしているがうまくいかない、というような内容もありました。私自身はローカルのPC上で動かす意外には業務でAWS上のMongoDBのクラスタ環境に触れる機会はあるのですが、それ以外に多くの環境で動作させた経験はなく、さらにOSもLinux, MacOS, Windowsなどによって細かい点はだいぶかわってくるので、様々な環境の理解や、MongoDBがどのように動作するかの詳細の理解が全く足りていないなぁと感じました。コースの範囲としてどこまでカバーするかというのはありますが、TAとして関わる以上はできるだけ助けになるような回答をしたいと思っています。

  3. MongoDBの詳細な仕様について理解することが重要

     質問では、コースのビデオの中では解説していない細かい点についても質問がくることがあります。例えばexplainの出力のこの項目はこっちの項目とどう違うんだ、とか、こういう用途でMongoDBを使う場合は、モデリングはどっちが向いているのか、など。自分がユーザとして使う分には知らなくても特に困らないような問題でも、やはりTAとして回答するには色々理解している必要があります。また、前項の内容に関連して、詳細を理解していないと他の環境で使ったときの動作がわからないということも出てきます。

  4. 英語力重要

     TAとしては一応日本語担当なのですが、日本語でのポストもありませんでしたし、TAである以上は英語のポストにも回答していきたいと思うのですが、まだまだ自分の英語力は不足していて、質問の意図を理解しきれなかったり、理解するまでに時間がかかってしまうことが多く、他のTAの方にお任せするということになって悔しい思いをしました。もちろん英語力だけでなく前述の技術的なところの不足に起因する部分も多いのですが、やはり英語ができないことでのデメリットを痛感しました。TAのオファーをいただいたときにも、最初にMongoDBの方とSkypeでミーティングをしたのですが、もちろん英語でのミーティングで、私の受け答えもかなり怪しかったので、MongoDBの方を不安にさせたのではないかと思っています。。

     受講する側で考えたときも、今回のコースについてはビデオは日本語でのボイスオーバーが提供されているものの、各週のホームワークや最終試験はすべて英語ですので、技術的には理解していても設問が理解できないと回答できません。フォーラムでのやりとりも参考になるものも多いので、やはり英語ができることでのメリットは大きいです。

  5. アウトプット重要

     TAのオファーをいただいたとき、受けるかどうかかなり迷いました。MongoDBの知識に自信があったわけでもありませんでしたし、ユーザグループで積極的に活動していたわけでもなかったので、務まるだろうかとかなり不安でした。でも逆にこれによってMongoDBのことをさらに知る機会にもなるかもしれないし、英語力をアップさせるためにも英語を使う機会を増やしたいと思っていたので、思い切って受けることにしました。結果としてはやって良かったと思っています。コース期間中は日々フォーラムをチェックする必要がありましたし、受講者の方は回答を待っているので気が休まりませんでしたが、おかげでアウトプットの機会も増え、また、このAdvent Calendarにも参加するためのネタにもなりました。もちろん自分で黙々とスキルアップに励む時間も大切なのですが、アウトプットすることによって得られることも多いので、今後も機会があれば積極的にアウトプットする場を作っていきたいと思っています。

 M101Pコースは11/25からまたスタートしています。MongoDBについて一通り学習するにはとても良いコンテンツだと思いますので、興味のある方は是非受講してみてください。前回のコースで日本語での質問がなかったので今回のコースでは日本語対応のTAはアサインされていませんが、ビデオは日本語対応されていますので、英語が不安な方も大丈夫だと思います。

 以上、MongoDB Advent Calendar 2013 5日目でした。

posted by Png akanuma on Thu 5 Dec 2013 at 08:34

iPhone 5 Retina の解像度で画面一杯の画像を左に1ドットずらす事を考える

つまり 320 x 568 x 4 X 4 = 2908160 バイト数分のコピー

   1  		for/memmove		機種		Debug	Release	(nano sec)
   2  int32	for		forward	5		7798625	3169792
   3  int32	for		reverse	5		6819542	2541750
   4  float		for		forward	5		8200625	2440875
   5  float		for		reverse	5		5783625	2477167
   6  int32	mem		forward	5		1105375	1394833
   7  int32	mem		reverse	5		894833	1158125
   8  float		mem		forward	5		1085958	1128250
   9  float		mem		reverse	5		896542	946042
  10  
  11  
  12  int32	for		forward	5s		4817875	943250
  13  int32	for		reverse	5s		4419667	976417
  14  float		for		forward	5s		4793042	893958
  15  float		for		reverse	5s		4415625	934625
  16  int32	mem		forward	5s		593083	628042
  17  int32	mem		reverse	5s		644542	582750
  18  float		mem		forward	5s		565583	605958
  19  float		mem		reverse	5s		561875	594792
  20  

ちなみに Accelerate Framework の vDSP_vsaddX で0を足すという強引な手でやると、以下のような結果。(オーバーラップしているときは使える保証がないので参考までに) 5s の Accelerate が何故かすごく早くなっていて、OpenGL を使ったときのような発熱がある。GPU ベースに書き直したのか?

   1  int32	DSP		reverse	5		1764667	2197000
   2  int32	DSP		reverse	5s		329292	393375
   3  float		DSP		reverse	5		1083958	1291875
   4  float		DSP		reverse	5s		325875	421125
   5  

ソース

   1  inline	void
   2  SpeedTest()
   3  {
   4  //	Normal
   5  	{	int32_t		wNum = 320 * 568 * 4;
   6  		int32_t*	w = new int32_t[ wNum + 1 ];
   7  		for ( int32_t i = 0; i <= wNum; i++ ) w[ i ] = i;
   8  		Timer		wTimer;
   9  		for ( int32_t i = 0; i < wNum; i++ ) w[ i ] = w[ i + 1 ];
  10  		NSLog( @"Int32 Forward:%zd", wTimer.Nano() );
  11  		for ( size_t i = 0; i < wNum; i++ ) assert( w[ i ] == i + 1 );
  12  		delete[] w;
  13  	}
  14  	{	int32_t		wNum = 320 * 568 * 4;
  15  		int32_t*	w = new int32_t[ wNum + 1 ];
  16  		for ( int32_t i = 0; i <= wNum; i++ ) w[ i ] = i;
  17  		Timer		wTimer;
  18  		int32_t		wCounter = wNum;
  19  		while ( wCounter-- ) w[ wCounter + 1 ] = w[ wCounter ];
  20  		NSLog( @"Int32 Reverse:%zd", wTimer.Nano() );
  21  		for ( size_t i = 1; i <= wNum; i++ ) assert( w[ i ] == i - 1 );
  22  		delete[] w;
  23  	}
  24  	{	int32_t		wNum = 320 * 568 * 4;
  25  		float*		w = new float[ wNum + 1 ];
  26  		for ( int32_t i = 0; i <= wNum; i++ ) w[ i ] = i;
  27  		Timer		wTimer;
  28  		for ( int32_t i = 0; i < wNum; i++ ) w[ i ] = w[ i + 1 ];
  29  		NSLog( @"Float Forward:%zd", wTimer.Nano() );
  30  		for ( size_t i = 0; i < wNum; i++ ) assert( w[ i ] == i + 1 );
  31  		delete[] w;
  32  	}
  33  	{	int32_t		wNum = 320 * 568 * 4;
  34  		float*		w = new float[ wNum + 1 ];
  35  		for ( int32_t i = 0; i <= wNum; i++ ) w[ i ] = i;
  36  		Timer		wTimer;
  37  		int32_t		wCounter = wNum;
  38  		while ( wCounter-- ) w[ wCounter + 1 ] = w[ wCounter ];
  39  		NSLog( @"Float Reverse:%zd", wTimer.Nano() );
  40  		for ( size_t i = 1; i <= wNum; i++ ) assert( w[ i ] == i - 1 );
  41  		delete[] w;
  42  	}
  43  //	Memmove
  44  	{	int32_t		wNum = 320 * 568 * 4;
  45  		int32_t*	w = new int32_t[ wNum + 1 ];
  46  		for ( int32_t i = 0; i <= wNum; i++ ) w[ i ] = i;
  47  		Timer		wTimer;
  48  		memmove( w, w + 1, wNum * 4 );
  49  		NSLog( @"Int32 Memmove	Forward:%zd", wTimer.Nano() );
  50  		for ( size_t i = 0; i < wNum; i++ ) assert( w[ i ] == i + 1 );
  51  		delete[] w;
  52  	}
  53  	{	int32_t		wNum = 320 * 568 * 4;
  54  		int32_t*	w = new int32_t[ wNum + 1 ];
  55  		for ( int32_t i = 0; i <= wNum; i++ ) w[ i ] = i;
  56  		Timer		wTimer;
  57  		memmove( w + 1, w, wNum * 4 );
  58  		NSLog( @"Int32 Memmove	Reverse:%zd", wTimer.Nano() );
  59  		for ( size_t i = 1; i <= wNum; i++ ) assert( w[ i ] == i - 1 );
  60  		delete[] w;
  61  	}
  62  	{	int32_t		wNum = 320 * 568 * 4;
  63  		float*		w = new float[ wNum + 1 ];
  64  		for ( int32_t i = 0; i <= wNum; i++ ) w[ i ] = i;
  65  		Timer		wTimer;
  66  		memmove( w, w + 1, wNum * 4 );
  67  		NSLog( @"Float Memmove	Forward:%zd", wTimer.Nano() );
  68  		for ( size_t i = 0; i < wNum; i++ ) assert( w[ i ] == i + 1 );
  69  		delete[] w;
  70  	}
  71  	{	int32_t		wNum = 320 * 568 * 4;
  72  		float*		w = new float[ wNum + 1 ];
  73  		for ( int32_t i = 0; i <= wNum; i++ ) w[ i ] = i;
  74  		Timer		wTimer;
  75  		memmove( w + 1, w, wNum * 4 );
  76  		NSLog( @"Float Memmove	Reverse:%zd", wTimer.Nano() );
  77  		for ( size_t i = 1; i <= wNum; i++ ) assert( w[ i ] == i - 1 );
  78  		delete[] w;
  79  	}
  80  //	DSP
  81  	{	int32_t		wNum = 320 * 568 * 4;
  82  		int*		w = new int[ wNum + 1 ];
  83  		for ( int32_t i = 0; i <= wNum; i++ ) w[ i ] = i;
  84  		Timer		wTimer;
  85  		int			wZero = 0;
  86  		vDSP_vsaddi( w + 1, 1, &wZero, w, 1, wNum );
  87  		NSLog( @"Int32 vDSP_vsaddi Rev:%zd", wTimer.Nano() );
  88  		for ( size_t i = 0; i < wNum; i++ ) assert( w[ i ] == i + 1 );
  89  		delete[] w;
  90  	}
  91  	{	int32_t		wNum = 320 * 568 * 4;
  92  		float*	w = new float[ wNum + 1 ];
  93  		for ( int32_t i = 0; i <= wNum; i++ ) w[ i ] = i;
  94  		Timer		wTimer;
  95  		float		wZero = 0;
  96  		vDSP_vsadd( w + 1, 1, &wZero, w, 1, wNum );
  97  		NSLog( @"Float vDSP_vsadd Rev:%zd", wTimer.Nano() );
  98  		for ( size_t i = 0; i < wNum; i++ ) assert( w[ i ] == i + 1 );
  99  		delete[] w;
 100  	}
 101  }
 102  

posted by Face Saturn on Mon 2 Dec 2013 at 17:20