Passenger + Nginx でアプリケーションを起動しようとしたときに、下記のようなエラーが出ました。
1 git://github.com/nzi fnab/msgpa ck-rails.g it (at master) is not checked out. Please run `bundle install` (Bundler::G itError)
もちろん bundle install は実行済みです。msgpack-ra
1 gem 'msgpack-rails', :git => 'git://gith ub.com/nzi fnab/msgpa ck-rails.g it'
bundle install --deployme
1 bundle package --all
とすることで、git からの gem も見つけてくれるようになりました。
(参考サイト)deploying rails3 apps with bundler and phusion passenger:
今まではネイティブのクライアントへJsonでレスポンスを返していたのですが、新しいサービスを開発する際に、レスポンスをMessagePac
ちなみにリクエストまでMessagePac
検証用アプリ
構成は下記の通りです。
- Nginx
- Passenger
- Ruby2.0
- Rails4
今回の検証はJson圧縮とMessagePac
(参考) JSONの替わりに使ったOJが速い
(参考) MsgpackRai
1 gem 'oj' 2 gem 'msgpack-rails', :git => 'git://gith ub.com/nzi fnab/msgpa ck-rails.g it'
Controller
1 class StaticPagesControlle r < Applicatio nControlle r 2 3 require 'oj' 4 5 after_acti on :testcompre ss 6 7 respond_to :json, :mpac 8 9 SERIALIZE_ TEST_RESPO NSE = {(省略)} 10 11 def serialize_ test 12 respond_to do |format| 13 format.json { render :json => SERIALIZE_ TEST_RESPO NSE } 14 format.mpac { render :text => SERIALIZE_ TEST_RESPO NSE.to_msgpack , :content_ty pe => 'applicatio n/x-mpac' } 15 end 16 end 17 18 def testcompre ss 19 response.body = ActiveSupp ort::Gzip.compress(response.body) 20 end 21 22 end
- oj が追加されていると、MultiJSONによって自動的に oj が選択されるようです。
- シリアライズするHashデータを SERIALIZE_
TEST_RESPO NSE として定義しておきます。 - 処理内容としてはテスト用のデータをシリアライズして返すだけです。
- また、今回はシリアライズ+圧縮を試すのですが、Rails側で圧縮した場合と、Nginxで圧縮した場合も比較してみます。Rails側で圧縮する場合の処理を testcompre
ss メソッドに定義しておいて、after_acti on で gzip 処理を行います。Nginx側で圧縮する場合は after_acti on をコメントアウトします。
検証方法としては、JMeterサーバを3つ用意して、100スレッドから上記APIへかけられるだけ負荷をかけてみるというやり方です。Nginxで圧縮するケースではHTTPヘッダマネージャでAccept-Enc
1 gzip_typestext/plain text/css applicatio n/json applicatio n/x-javasc ript text/xml applicatio n/xml applicatio n/xml+rss text/javas cript applicatio n/x-mpac;
デフォルトでは applicatio
検証結果
検証結果は下記の通りです。
種別 | 圧縮 | サンプル数 | 平均 | 中央値 | 90%LINE | 最小値 | 最大値 | スループット | KB/sec |
---|---|---|---|---|---|---|---|---|---|
Json | Rails | 48626 | 1816 | 1805 | 2002 | 25 | 2258 | 158.57 | 306.30 |
MessagePac | Rails | 271372 | 317 | 321 | 356 | 5 | 533 | 889.05 | 1701.66 |
Json | Nginx | 48824 | 1785 | 1775 | 1988 | 27 | 2206 | 159.11 | 393.07 |
MessagePac | Nginx | 333783 | 249 | 248 | 279 | 4 | 466 | 1093.61 | 2395.42 |
MessagePac | 非圧縮 | 346175 | 246 | 246 | 271 | 6 | 610 | 1134.33 | 19178.19 |
Jsonの場合はCPUは常に振り切っている状態でしたが、MessagePac
テスト結果のスループットとしてはMessagePac
仕事で新しいサービスのDBを何にするか検討していて、MongoDBと同じような使い方ができてサーバコストが抑えられるものがないか探していたときにCouchbaseのことを思い出してハンズオンセミナーなど受けてきました。結局Couchbaseは使わないことにしたのですが、主にMongoDBと比べてどうだったかといったあたりを書いておきます。
Couchbaseについてざっくりと
まずCouchbaseがどういったものかをざっくり説明しておくと、MongoDBと同じようなドキュメント指向のNoSQL製品で、JSONデータを格納します。
売りにしている特徴としては下記のような点が挙げられています。
- 容易なスケーラビリティ
- 安定したハイパフォーマンス
- 24時間365日安定稼働
- 柔軟なデータモデル
MongoDBと似ているところ
また、下記のような思想はMongoDBと共通しています。
- データは全部メモリに載せる
- パフォーマンス出すにはサーバを追加する(スケールアウト)
データが100GBあれば、masterノードのメモリの合計がそれ以上になるようにしましょう、という点では両者とも同じ思想で、サーバスペック的にもCPUよりもメモリを多く積んだサーバを必要とします。
比較して気になったところなど
逆にMongoDBと比較して違う点で気になったところ等は下記のようなところです。
項目 | MongoDB | Couchbase |
---|---|---|
レプリケーション | master-sla | master-mas |
ノード追加 | コマンドで設定変更 | Webの管理画面から操作可能 |
自動Failover | 台数制限なし | 同時に一台まで |
クエリ | SQLライクな柔軟なクエリ | MapReduceのみ |
ドキュメント削除時 | ドキュメント削除時にすべて消える | メタデータが残ってしまう |
商用版と無償版 | 違いはサポートの有無 | 商用版がベースなのでバージョンアップなどもまず商用版から |
それぞれ簡単に説明していきます。
master-mas
terレプリケーション MongoDBではレプリケーションの構成はmaster-sla
veになりますので、masterノードのレプリカはslaveサーバが保持します。なのでシャーディング構成をとっている場合で1レプリカセット3台で構成している場合は、1シャード増やす場合には1レプリカセット追加ということになって3ノード増やす必要があります。これに対してCouchbaseはmaster-mas terレプリケーションなので、あるmasterノードのレプリカは他のmasterノードが保持します。なので1シャード追加する場合にも1ノード増やすだけでOKです。masterが他のmasterのレプリカを持つ分、1ノードあたりのデータ量はMongoDBと比べると多くなりますが、レプリカデータは普段は非Activeになっていてアクセスされないので、その分のメモリは足りていなくても運用できるとのことでした。(実際の動作検証はしてません。。) 管理画面からノード追加
MongoDBではクラスタへノードを追加する場合、コマンドラインから操作する必要がありますが、CouchbaseではWebの管理画面から操作することができます。追加後のリバランスもそのまま管理画面から実行可能で、データ量が増えてもリバランスの所要時間はあまり変わらないとのことです。また、Mongoではシャードキーを自分で設定しますが、Couchbaseでは自動で振り分けが行われるので、データの内容から置かれているサーバを特定することはできません。リバランス処理の負荷はやはり高いので、すでに負荷でいっぱいいっぱいの状態のクラスタでノード追加、リバランスを行うのはつらいものがあります。
自動Failoverは一台まで
MongoDBではレプリケーションがmaster-sla
ve構成ということもあり、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が利用できるようになったらパフォーマンス検証等行ってみたいと思います。ただ無償版の状況を考えるとなかなか難しいかもしれませんが。。
[Rails][MySQL] RailsでMySQLを使う
Ruby on Rails で普通にプロジェクトを作成すると sqlite3 が使われるようになっていますが、mysql を使用するにはプロジェクト作成時に -d オプションで mysql を指定します。
1 $ rails new turntable_test -d mysql 2 create 3 create README.rdo c 4 create Rakefile 5 create config.ru 6 create .gitignore 7 create Gemfile 8 create app 9 create app/assets /javascrip ts/applica tion.js 10 create app/assets /styleshee ts/applica tion.css 11 create app/contro llers/appl ication_co ntroller.r b 12 create app/helper s/applicat ion_helper .rb 13 create app/views/ layouts/ap plication. html.erb 14 create app/assets /images/.k eep 15 create app/mailer s/.keep 16 create app/models /.keep 17 create app/contro llers/conc erns/.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/rou tes.rb 25 create config/app lication.r b 26 create config/env ironment.r b 27 create config/env ironments 28 create config/env ironments/ developmen t.rb 29 create config/env ironments/ production .rb 30 create config/env ironments/ test.rb 31 create config/ini tializers 32 create config/ini tializers/ backtrace_ silencers. rb 33 create config/ini tializers/ filter_par ameter_log ging.rb 34 create config/ini tializers/ inflection s.rb 35 create config/ini tializers/ mime_types .rb 36 create config/ini tializers/ secret_tok en.rb 37 create config/ini tializers/ session_st ore.rb 38 create config/ini tializers/ wrap_param eters.rb 39 create config/loc ales 40 create config/loc ales/en.ym l 41 create config/boo t.rb 42 create config/dat abase.yml 43 create db 44 create db/seeds.r b 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/fav icon.ico 57 create public/rob ots.txt 58 create test/fixtu res 59 create test/fixtu res/.keep 60 create test/contr ollers 61 create test/contr ollers/.ke ep 62 create test/maile rs 63 create test/maile rs/.keep 64 create test/model s 65 create test/model s/.keep 66 create test/helpe rs 67 create test/helpe rs/.keep 68 create test/integ ration 69 create test/integ ration/.ke ep 70 create test/test_ helper.rb 71 create tmp/cache 72 create tmp/cache/ assets 73 create vendor/ass ets/javasc ripts 74 create vendor/ass ets/javasc ripts/.kee p 75 create vendor/ass ets/styles heets 76 create vendor/ass ets/styles heets/.kee p 77 run bundle install 78 Enter your password to install the bundled RubyGems to your system: 79 Fetching gem metadata from https://ru bygems.org /......... .. 80 Fetching gem metadata from https://ru bygems.org /.. 81 Resolving dependenci es... 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_saf e (0.1.3) 88 Using tzinfo (0.3.38) 89 Using activesupp ort (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 actionmail er (4.0.1) 100 Using activemode l (4.0.1) 101 Using activereco rd-depreca ted_finder s (1.0.3) 102 Using arel (4.0.1) 103 Using activereco rd (4.0.1) 104 Using bundler (1.3.5) 105 Using coffee-scr ipt-source (1.6.3) 106 Using execjs (2.0.2) 107 Using coffee-scr ipt (2.2.0) 108 Using thor (0.18.1) 109 Using railties (4.0.1) 110 Using coffee-rai ls (4.0.1) 111 Using hike (1.2.3) 112 Installing jbuilder (1.5.3) 113 Using jquery-rai ls (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 Installingmysql2 (0.3.14)
また、sqlite3は使用されなくなるので、下記のようなsqlite3に関する出力はなくなります。
1 Using sqlite3 (1.3.8)
作成される config/dat
1 $ diff test_app/config/data base.yml turntable_ test/confi g/database .yml 2 1,2c1 3 < # SQLite version 3.x 4 < # gem install sqlite3 5 --- 6 > # MySQL. Versions 4.1 and 5.0 are recommende d. 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/refma n/5.0/en/o ld-client. html 19 7,8c12,14 20 < adapter: sqlite3 21 < database: db/develop ment.sqlit e3 22 --- 23 > adapter: mysql2 24 > encoding: utf8 25 > database: turntable_ test_devel opment 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.sq lite3 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/product ion.sqlite 3 48 --- 49 > adapter: mysql2 50 > encoding: utf8 51 > database: turntable_ test_produ ction 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 --binstubs2 $ bin/rake db:create
mysqlにログインしてDBが作成されていることを確認します。
1 mysql> show databases; 2 +---------------------- ------+ 3 | Database | 4 +------------ ---------- ------+ 5 | informatio n_schema | 6 | mysql | 7 | performanc e_schema | 8 | test | 9 | turntable_ test_devel opment | 10 | turntable_ test_test | 11 +------------ ---------- ------+ 12 6 rows in set (0.00 sec)
動作確認用のサンプルとしてUserModelを作成します。
1 $ bin/rails generate model User name:string email:stri ng 2 invoke active_rec ord 3 create db/migrate /201312112 24446_crea te_users.r b 4 create app/models /user.rb 5 invoke test_unit 6 create test/model s/user_tes t.rb 7 create test/fixtu res/users. yml
migrate を実行してDBにテーブルを作成します
1 $ bin/rake db:migrate2 == CreateUser s: migrating ========== ========== ========== ========== ========== == 3 -- create_tab le(:users) 4 -> 0.0110s 5 == CreateUser s: migrated (0.0111s) ========== ========== ========== ========== ===
下記のようにテーブルが作成されていれば成功です。
1 mysql> desc users; 2 +------------+------ --------+- -----+---- -+-------- -+-------- --------+ 3 | Field | Type | Null | Key | Default | Extra | 4 +--------- ---+------ --------+- -----+---- -+-------- -+-------- --------+ 5 | id | int(11) | NO | PRI | NULL | auto_incre ment | 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)
[MySQL] MacにMySQLをインストール
私物の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/F ink and try again. 5 ==> Downloadin g https://do wnloads.sf .net/proje ct/machome brew/Bottl es/mysql-5 .6.1 6 ######################################################################## 100.0% 7 ==> Pouring mysql-5.6. 15.maveric ks.bottle. tar.gz 8 ==> Caveats 9 A "/etc/my.cn f" from another install may interfere with a Homebrew-b uilt 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/L aunchAgent s 17 Then to load mysql now: 18 launchctl load ~/Library/L aunchAgent s/homebrew .mxcl.mysq l.plist 19 Or, if you don't want/need launchctl, you can just run: 20 mysql.serv er start 21 ==> /usr/local /Cellar/my sql/5.6.15 /bin/mysql _install_d b --verbose --user=aka numa 22 ==> Summary 23 /usr/local /Cellar/my sql/5.6.15 : 9410 files, 349M
インストール後の各手順については上記のインストール時の出力に示されています。
まずOS起動時にMySQLが起動するように設定します。
1 $ ln -sfv /usr/local/opt/mysql /*.plist ~/Library/L aunchAgent s 2 /Users/aka numa/Libra ry/LaunchA gents/home brew.mxcl. mysql.plis t -> /usr/local /opt/mysql /homebrew. mxcl.mysql .plist
launchctlコマンドで起動します。
1 $ launchctl load ~/Library/LaunchAgent s/homebrew .mxcl.mysq l.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/my sql/5.6.15 /bin/mysql d --basedir= /usr/local /Cellar/my sql/5.6.15 --datadir= /usr/local /var/mysql --plugin-d ir=/usr/lo cal/Cellar /mysql/5.6 .15/lib/pl ugin --bind-add ress=127.0 .0.1 --log-erro r=/usr/loc al/var/mys ql/Hiroaki s-MacBook- Air.local. err --pid-file =/usr/loca l/var/mysq l/Hiroakis -MacBook-A ir.local.p id 5 akanuma 2467 0.0 0.0 2436436 1008 ?? S 10:54PM 0:00.02 /bin/sh /usr/local /opt/mysql /bin/mysql d_safe --bind-add ress=127.0 .0.1
mysqlコマンドで接続できることを確認します。
1 $ mysql -uroot 2 Welcome to the MySQL monitor. Commands end with ; or \g. 3 Your MySQL connectionid 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 Corporatio n 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.
仕事でRailsを使うことになり、APサーバの選定にあたってPuma, Unicorn, Passenger の比較検討を行いました。方法としてはJMeterでAPサーバにデプロイしたRailsアプリケーションに対して負荷をかけられるだけかけるというやり方です。
試験環境
試験の環境としては下記の構成です。
- Ruby2.0, Rails4
- アプリケーションサーバ:1台(VM)
- JMeterサーバ:3台(VM)
- JMeterクライアント:1台(通常の作業PC)
サーバ構成 hostaname CPU 仮想コア数(Per CPU) Memory Disk 用途 loadtest01 2 4 8192MB 20GB APサーバ loadtest02 1 1 4096MB 20GB JMeterサーバ loadtest03 1 1 4096MB 20GB JMeterサーバ loadtest04 1 1 4096MB 20GB JMeterサーバ
サーバアーキテクチャの比較
各APサーバのアーキテクチャの比較は下記の通りです。
Puma | Unicorn | Passenger | |
---|---|---|---|
デプロイモデル | Reverse Proxy | Reverse Proxy | Nginx Module |
Process/Th | Multi Processes / Multi Threads | Multi Processes / Single Thread | Multi Processes / Single Thread(OSS Edition) |
Multi Processes / Multi Threads(Commercial | |||
その他特徴 | Workerプロセスごとにスレッドを立ち上げる | Pull型(Workerプロセス側からキューのタスクを取得しにいく) | トラフィックに応じてプロセス数を自動調整。商用Editionのみマルチスレッド利用可能 |
処理内容
処理内容としては、純粋にAPサーバの処理能力の比較にするため、DBアクセス等はせず、フィボナッチ数列を計算して返す処理を行います。ロジックは書きサイトを参考にしました。
1 class StaticPagesControlle r < Applicatio nControlle r 2 3 def fibonacci 4 @fibonacci = calc_fibon acci(params[:n].to_i) 5 end 6 7 private 8 9 def calc_fibon acci(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
ワーカプロセス数 | スレッド数 | サンプル数 | 平均値(msec) | 中間値(msec) | 90%LINE (msec) | 最小値(msec) | 最大値(msec) | スループット(/sec) | KB/sec | エラー数 |
---|---|---|---|---|---|---|---|---|---|---|
1 | 500 | 43079 | 2090 | 1978 | 2379 | 129 | 2519 | 141.68 | 414.67 | 多発 |
100 | 8 | 241811 | 365 | 57 | 1071 | 6 | 46414 | 799.43 | 2339.49 | 0 |
100 | 32 | 243620 | 362 | 60 | 1136 | 6 | 16890 | 805.37 | 2356.83 | 32 |
64 | 32 | 242825 | 365 | 51 | 1355 | 7 | 103157 | 802.35 | 2347.94 | 1 |
32 | 32 | 241934 | 366 | 46 | 760 | 6 | 252504 | 798.24 | 2335.89 | 0 |
16 | 64 | 237270 | 373 | 31 | 339 | 6 | 298546 | 782.00 | 2288.34 | 0 |
16 | 96 | 231996 | 382 | 37 | 644 | 6 | 241748 | 762.49 | 2231.26 | 0 |
- ワーカプロセス数1のケースでは全くCPUを使い切れていませんが、他のケースではCPUを使い切った状態で動いていました。
- スレッド数を増加させてもリソースの使用量はあまり変化しませんでしたが、プロセス数を増やすと使用量がかなり増えます。
- メモリに余裕があってもCPUが振り切る方が早いです。
Unicorn
ワーカプロセス数 | サンプル数 | 平均値(msec) | 中間値(msec) | 90%LINE (msec) | 最小値(msec) | 最大値(msec) | スループット(/sec) | KB/sec | エラー数 |
---|---|---|---|---|---|---|---|---|---|
1 | 4294 | 11345 | 572 | 50553 | 77 | 176247 | 11.74 | 34.54 | 433 |
8 | 8686 | 6026 | 562 | 13721 | 77 | 141193 | 22.15 | 65.15 | 389 |
32 | 42943 | 1465 | 325 | 2844 | 51 | 183552 | 103.48 | 304.38 | 280 |
64 | 191254 | 440 | 170 | 375 | 26 | 115819 | 498.54 | 1466.46 | 48 |
96 | 247191 | 360 | 170 | 261 | 49 | 82454 | 773.58 | 2275.69 | 3 |
128 | 256050 | 343 | 252 | 452 | 13 | 20587 | 836.00 | 2459.82 | 0 |
160 | 254117 | 347 | 291 | 537 | 15 | 8825 | 835.54 | 2458.54 | 0 |
192 | 252158 | 349 | 302 | 588 | 13 | 5746 | 833.94 | 2453.87 | 0 |
224 | 229723 | 383 | 275 | 592 | 9 | 299799 | 697.12 | 2045.15 | 2 |
- ワーカプロセス数が1の場合は1CPUしか使われない
- ワーカプロセス数をコア数と同じ8にした場合でも、すべてのコアが使われるわけではない
- ワーカプロセス数をコア数の4倍の64まで増やすとかなりCPUが使われるようになってきて、128まで増やすとCPUはほぼ使い切られて、メモリの使用率も高くなる。
- ワーカプロセス数を192まで増やした時点でメモリもほぼ使い切られ、224まで増やすとメモリが足りなくなり、Swapが発生して遅くなる。
Passenger
プロセス数 | サンプル数 | 平均値(msec) | 中間値(msec) | 90%LINE (msec) | 最小値(msec) | 最大値(msec) | スループット(/sec) | KB/sec | エラー数 |
---|---|---|---|---|---|---|---|---|---|
1 | 65188 | 1376 | 1282 | 1325 | 1224 | 8690 | 215.15 | 646.07 | 0 |
8 | 245153 | 360 | 355 | 368 | 297 | 2441 | 810.57 | 2433.99 | 0 |
16 | 246737 | 357 | 353 | 381 | 279 | 2339 | 815.77 | 2448.77 | 0 |
24 | 241165 | 366 | 360 | 391 | 281 | 2414 | 797.27 | 2392.30 | 0 |
- プロセス数が1だと4コアしか使われていない
- プロセス数をコア数と同じにするとどのコアもかなり使われるようになる。メモリは余裕あり。
- プロセス数をコア数以上に増やしていってもCPU, メモリの使用率はあまり変化しない。CPUは80%まで使われることが多いが、アイドルが10%前後残っていて、使い切ることはない
各APサーバの最適値同士を比較
各APサーバの最適値同士を比較して、使用するAPサーバを決定する
APサーバ | サンプル数 | 平均値(msec) | 中間値(msec) | 90%LINE (msec) | 最小値(msec) | 最大値(msec) | スループット(/sec) | KB/sec |
---|---|---|---|---|---|---|---|---|
Puma | 241811 | 365 | 57 | 1071 | 6 | 46414 | 799.43 | 2339.49 |
Unicorn | 252158 | 349 | 302 | 588 | 13 | 5746 | 833.94 | 2453.87 |
Passenger | 246737 | 357 | 353 | 381 | 279 | 2339 | 815.77 | 2448.77 |
- Pumaはスループットが一番低いのに加えて最大値、90%LINEの遅さ、不安定さで候補から除外。
- Passengerは安定しているが最小値が他の2つに比べて大きい。90%LINE、最大値は優秀。
- Unicornはスループットが一番大きいのと、最小値、平均値も悪くない。最大値、90%LINEがPassengerと比 べると遅いのが懸念点。
APサーバ | サンプル数 | 平均値(msec) | 中間値(msec) | 90%LINE (msec) | 最小値(msec) | 最大値(msec) | スループット(/sec) | KB/sec |
---|---|---|---|---|---|---|---|---|
Unicorn | 23075 | 12 | 10 | 13 | 4 | 116 | 75.32 | 221.50 |
Passenger | 23092 | 11 | 9 | 12 | 5 | 118 | 75.38 | 226.67 |
- UnicornとPassengerでほぼ差はなし
- Passengerは最初の比較時に他と比べて最小値が遅いことが懸念だったが、実際の状況に近いアクセス数では 問題なく、Unicornが負荷が低い状態でもメモリリソースを消費するのに対してPassengerはリソースの消費が少ないこと から、Passengerを第一候補と考える。
検証時の問題点
検証時に発生した問題点を参考までに記載しておきます。
Passenger
- デフォルトの状態で負荷をかけたところ、下記のようなエラーが多発してHTTPステータスコード 503が返される。
1 [ 2013-11-2012:21:13.0 578 17419/7fbb bc9da700 Pool2/Grou p.h:331 ]: Request queue is full. Returning an error
リクエストのQueueがあふれたことによるもののようです。Queue のサイズのデフォルトは100なので、無制限にするために passenger_
Unicorn
- Nginxとの連携をUnixドメインソケットで行う設定で負荷をかけたところ、下記エラーが多発。
1 2013/11/2011:32:35 [error] 27462#0: *622742 connect() to unix:///ho me/test_us er/server- proto/unic orn.sock failed (11: Resource temporaril y unavailabl e) while connecting to upstream, client: 192.168.51 .234, server: loadtest01 .test.com, request: "GET /fibonacci ?n=1000 HTTP/1.1", upstream: "http://uni x:///home/ test_user/ server-pro to/unicorn .sock:/fib onacci?n=1000", host: "loadtest01 .test.com: 3090"
下記サイトなどを参考に、TCPポートによる連携に変更しました。
変更後に負荷をかけたところ、エラーは概ね解消しました。負荷をかけ続けていると、件数は少なくなったもののエラーが発生しました。HTTPステータスコードは 502 Bad Gateway
1 2013/11/2014:45:29 [error] 25979#0: *254606 upstream prematurel y 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:307 0/fibonacc i?n=1000", host: "loadtest01 .test.com: 3090"
1 [2013-11-2014:44:09.1 09250] ERROR worker=114 PID:26737 timeout (31s > 30s), 2 killing 3 [2013-11-20 14:44:09.1 19595] ERROR reaped #<Process::S tatus: pid 26737 SIGKILL 4 (signal 9)> worker=114 5 [2013-11-20 14:44:10.1 40398] INFO worker=114 ready
検証のためにUnicorn側のタイムアウト設定を大幅に増やしました。30秒 → 300秒に変更。Nginxの設定にも下記を追加しました。
1 send_timeout 300; 2 proxy_conn ect_timeou t 300; 3 proxy_read _timeout 300;
これでひとまずエラーは回避できました。。スループットの検証のためにタイムアウト値を大きくしていますが、実際の環境ではタイムアウト値は小さく設定する必要があります。
MongoDB Advent Calendar 2013 の 5日目です。
先日、MongoDB University
TA(Teaching Assistant) というのはコース内のフォーラムで、受講している生徒からの質問等に回答するのが主な役割です。以前は英語版のコースのみだったのですが、Pythonを使用したプログラミングの学習コース(M101P)のビデオが日本語でも提供されることになり、日本語を話す受講者のために日本人のTAが必要ということになりました。MongoDB University
日本語での生徒からの投稿ゼロ
まず結果として日本語でのフォーラムへの投稿数はというと、残念ながら0でした。日々フォーラムをチェックしていると、英語でのポストはどんどん増え、活発にやりとりされていくのですが、日本語でのポストはないまま終わってしまいました。そもそもJapanese Speakerの受講者がEnglish Speakerに比べてかなり少ないということはあると思いますが、こういったところでの積極性はやはり日本人よりも欧米の方々の方が強いのかなぁと思いました。
使用しているバージョンや環境などによる違いが大きい
M101PはPythonを使用したプログラミングについてのコースで、前提としているPythonのバージョンは2.7であることも記載されているのですが、3系を使用しているために提供されているコードが正しく動作しないというポストがかなり多く見られました。また、シンプルにローカルのPC上で動かす意外にも、OpenShift などのクラウド環境で動かそうとしているがうまくいかない、というような内容もありました。私自身はローカルのPC上で動かす意外には業務でAWS上のMongoDBのクラスタ環境に触れる機会はあるのですが、それ以外に多くの環境で動作させた経験はなく、さらにOSもLinux, MacOS, Windowsなどによって細かい点はだいぶかわってくるので、様々な環境の理解や、MongoDBがどのように動作するかの詳細の理解が全く足りていないなぁと感じました。コースの範囲としてどこまでカバーするかというのはありますが、TAとして関わる以上はできるだけ助けになるような回答をしたいと思っています。
MongoDBの詳細な仕様について理解することが重要
質問では、コースのビデオの中では解説していない細かい点についても質問がくることがあります。例えばexplainの出力のこの項目はこっちの項目とどう違うんだ、とか、こういう用途でMongoDBを使う場合は、モデリングはどっちが向いているのか、など。自分がユーザとして使う分には知らなくても特に困らないような問題でも、やはりTAとして回答するには色々理解している必要があります。また、前項の内容に関連して、詳細を理解していないと他の環境で使ったときの動作がわからないということも出てきます。
英語力重要
TAとしては一応日本語担当なのですが、日本語でのポストもありませんでしたし、TAである以上は英語のポストにも回答していきたいと思うのですが、まだまだ自分の英語力は不足していて、質問の意図を理解しきれなかったり、理解するまでに時間がかかってしまうことが多く、他のTAの方にお任せするということになって悔しい思いをしました。もちろん英語力だけでなく前述の技術的なところの不足に起因する部分も多いのですが、やはり英語ができないことでのデメリットを痛感しました。TAのオファーをいただいたときにも、最初にMongoDBの方とSkypeでミーティングをしたのですが、もちろん英語でのミーティングで、私の受け答えもかなり怪しかったので、MongoDBの方を不安にさせたのではないかと思っています。。
受講する側で考えたときも、今回のコースについてはビデオは日本語でのボイスオーバーが提供されているものの、各週のホームワークや最終試験はすべて英語ですので、技術的には理解していても設問が理解できないと回答できません。フォーラムでのやりとりも参考になるものも多いので、やはり英語ができることでのメリットは大きいです。
アウトプット重要
TAのオファーをいただいたとき、受けるかどうかかなり迷いました。MongoDBの知識に自信があったわけでもありませんでしたし、ユーザグループで積極的に活動していたわけでもなかったので、務まるだろうかとかなり不安でした。でも逆にこれによってMongoDBのことをさらに知る機会にもなるかもしれないし、英語力をアップさせるためにも英語を使う機会を増やしたいと思っていたので、思い切って受けることにしました。結果としてはやって良かったと思っています。コース期間中は日々フォーラムをチェックする必要がありましたし、受講者の方は回答を待っているので気が休まりませんでしたが、おかげでアウトプットの機会も増え、また、このAdvent Calendarにも参加するためのネタにもなりました。もちろん自分で黙々とスキルアップに励む時間も大切なのですが、アウトプットすることによって得られることも多いので、今後も機会があれば積極的にアウトプットする場を作っていきたいと思っています。
M101Pコースは11/25からまたスタートしています。MongoDBについて一通り学習するにはとても良いコンテンツだと思いますので、興味のある方は是非受講してみてください。前回のコースで日本語での質問がなかったので今回のコースでは日本語対応のTAはアサインされていませんが、ビデオは日本語対応されていますので、英語が不安な方も大丈夫だと思います。
以上、MongoDB Advent Calendar 2013 5日目でした。
Memory のコピー
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
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:%z d", 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:%z d", 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:%z d", 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:%z d", 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:%z d", 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:%z d", 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:%z d", 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_vsadd i( w + 1, 1, &wZero, w, 1, wNum ); 87 NSLog( @"Int32 vDSP_vsadd i 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