きたるISUCON6向け武者修行その2 -MySQLのチューニングをしてみよう-
kitakitabauer.hatenablog.com
前回プロビジョニングしたリポジトリにインストールされたMySQLの設定を確認していじっていきます。
MySQLのバージョンを確認
$ mysql --version mysql Ver 14.14 Distrib 5.6.31, for Linux (x86_64) using EditLine wrapper
執筆時点で5.6系の最新なので問題はなさそう
設定ファイルを読み込む優先順位も念のため確認
$ mysql --help | grep my.cnf order of preference, my.cnf, $MYSQL_TCP_PORT, /etc/my.cnf /etc/mysql/my.cnf /usr/etc/my.cnf ~/.my.cnf
/etc/my.cnfが優先される(というかこれしかなかった)
my.cnfが複数ある場合は、後から読み込まれたファイルで値が上書きされるので注意が必要。
下記オプションで設定ファイルを直接指定して、
他のファイルを読み込まないようにするのが定石か?$ /etc/init.d/mysql --defaults-file=/etc/my.cnf --user=mysql
my.cnfの設定確認と修正
$ sudo vi /etc/my.conf
# スロークエリログ出力ON slow_query_log # スロークエリログファイル slow_query_log_file=/var/log/mysql/slow.sql # クエリが要した時間がこのしきい値の秒数を超えたらログを出力する(ここでは3秒) long_query_time=3
スロークエリ用のログディレクトリとログファイルを作成
$ sudo mkdir /var/log/mysql $ sudo touch /var/log/mysql/slow.sql # コロン以降を指定しないと、所有権を変更するユーザが所属するログイングループを指定したことになるので、 # わざわざグループを調べなくてもよいので少し便利 $ sudo chown -R mysql: /var/log/mysql
きたるISUCON6向け武者修行その1 -仮想環境を構築して負荷試験を実行してみよう-
強い意志の元、今年こそISUCONに参加しようと思います。
ISUCONとは?
お題となるWebサービスを決められたレギュレーションの中で限界まで高速化を図るチューニングバトル、それがISUCON
そこで、まずはチームメンバーとISUCON4(2014年開催)の復習をしようということになりました。
ISUCONの過去問を構築するためのVagrantfileを用意してくださった素晴らしい方のリポジトリがあるので、そちらをベースに仮想環境を構築して進めていこうと思います。
github.com
1.Vagrantfileを準備する
前述のリポジトリをcloneし、isucon4-qualifierにcdします。
その階層にVagrantfileが用意されています。
$ git clone https://github.com/matsuu/vagrant-isucon
$ cd isucon4-qualifier
2.ローカルにVirtualBoxとVagrantをインストール
ここでは手軽にbrew caskでインストールします。
$ brew cask install virtualbox $ brew cask install vagrant
VirtualBoxは、仮想的なPCを作成し、特定のOSをインストール・実行できるソフトウェアです。
Vagrantは、仮想環境の構築と制御を行うソフトウェアです。
検証環境は下記の通りです。
- Mac OS X 10.9.5
- VirtualBox 5.0.20
- Vagrant 1.8.4
3.起動・プロビジョニング
Vagrantfileがあるディレクトリで下記コマンドにて環境を構築していきます。
$ vagrant up
するとエラーが発生しました。
Failed to load R0 module /Applications/VirtualBox.app/Contents/MacOS/VMMR0.r0: World writable: '/Applications' (VERR_SUPLIB_WORLD_WRITABLE). Failed to load VMMR0.r0 (VERR_SUPLIB_WORLD_WRITABLE).
どうやらVirtualBoxのバグらしく、下記コマンドを実行して再度vagrant upしたらすんなり通りました。
# VirtualBox.app配下全てについて、権限がないユーザ・グループ以外のユーザに対して書き換えを禁止する $ sudo chmod -R o-w /Applications/VirtualBox.app/ # パーミッションが正しくない場合は修復する $ sudo /usr/libexec/repair_packages --repair --standard-pkgs --volme / --debug
4.VagrantインスタンスにSSHでログインする
$ vagrant ssh Last login: Wed Jun 22 04:08:36 2016 from 10.0.2.2 [vagrant@localhost ~]$
5.isuconユーザにスイッチする
[vagrant@localhost ~]$ sudo su - isucon # 構成はこんな感じ [isucon@localhost ~]$ ll -lrt total 8984 drwxr-xr-x. 4 isucon isucon 4096 Jun 22 03:10 gocode drwxr-xr-x. 9 isucon isucon 4096 Jun 22 03:29 webapp -rwxr-xr-x. 1 isucon isucon 428 Jun 22 03:29 init.sh -rwxr-xr-x. 1 isucon isucon 734 Jun 22 03:29 env.sh -rwxr-xr-x. 1 isucon isucon 9177962 Jun 22 03:29 benchmarker drwxr-xr-x. 2 isucon isucon 4096 Jun 22 03:30 sql
この用意されたbenchmarkerを使ってサーバに負荷をかけて、スコアを競います。
6.参考実装の言語切り替え
今回我々のチームはpythonで戦う予定なので、まずはpythonでベンチマークを実行してみることとします。
デフォルトがRubyになっているので、supervisordを終了させます。
※Supervisorとは、常駐起動させたいスクリプトなどを容易にプロセス管理/デーモン化するツールです。
$ supervisorctl stop isucon_ruby
次に、/etc/supervisord.confを編集します。
[isucon_ruby]をautostart=falseにします。
[program:isucon_ruby] directory=/home/isucon/webapp/ruby command=/home/isucon/env.sh foreman start user=isucon stdout_logfile=/tmp/isucon.ruby.log stderr_logfile=/tmp/isucon.ruby.log autostart=false
切り替えたい言語をautostart=trueにします。
pythonはgunicornでHTTPサーバを立ち上げているようです。
Gunicorn - Python WSGI HTTP Server for UNIX
[program:isucon_python] directory=/home/isucon/webapp/python command=/home/isucon/env.sh gunicorn -c gunicorn_config.py app:app user=isucon stdout_logfile=/tmp/isucon.python.log stderr_logfile=/tmp/isucon.python.log autostart=true
最後にpythonにてsupervisordをstartさせます。
$ supervisorctl start isucon_python
supervisordは、よくあるinit.d配下での管理ではなかったことを知りました
/etc/init.d/supervisord stop
/etc/init.d/supervisord start
7.ベンチマーク実行
[isucon@localhost ~]$ ./benchmarker b 09:52:07 type:info message:launch benchmarker 09:52:07 type:warning message:Result not sent to server because API key is not set 09:52:07 type:info message:init environment 09:52:10 type:info message:run benchmark workload: 1 09:52:29 type:fail reason:Connection timeout method:GET uri:/images/isucon-bank.png 09:52:39 type:fail reason:Connection timeout method:POST uri:/login 09:52:49 type:fail reason:Connection timeout method:GET uri:/ 09:52:59 type:fail reason:Connection timeout method:GET uri:/ 09:53:09 type:fail reason:Connection timeout method:GET uri:/ 09:53:19 type:fail reason:Request cancelled because benchmark finished (1min) method:GET uri:/ 09:53:19 type:info message:finish benchmark workload: 1 09:53:24 type:info message:check banned ips and locked users report 09:54:24 type:fail reason:Request cancelled because benchmark finished (1min) message:Report checking is failed. Do not send score. 09:54:24 type:score success:654 fail:6 score:142
無事pythonで実行できて、スコアも表示されたけど、failばかり…
しばらく調べてみると、pythonのプロセスがおかしいことに気づきました。
$ ps aux | grep unicorn isucon 13124 0.0 1.6 247772 31332 ? Sl 08:25 0:00 unicorn master -c unicorn_config.rb -p 8080 isucon 13154 0.0 2.0 259700 39108 ? Sl 08:25 0:00 unicorn worker[0] -c unicorn_config.rb -p 8080 isucon 13157 0.0 1.9 258840 38304 ? Sl 08:25 0:00 unicorn worker[1] -c unicorn_config.rb -p 8080 …
謎のunicornプロセスが起動しています。
"gunicorn"が正式名称です。
masterプロセスをkillして、再度supervisorで起動します。
$ sudo pkill -f 'unicorn' $ supervisorctl restart isucon_python
ちゃんとgunicornのプロセスが起動しています。
$ ps aux | grep unicorn isucon 16685 0.1 0.7 200628 14740 ? S 10:03 0:00 /home/isucon/.local/python/bin/python2.7 /home/isucon/.local/python/bin/gunicorn -c gunicorn_config.py app:app isucon 16690 0.0 1.0 231740 19476 ? S 10:03 0:00 /home/isucon/.local/python/bin/python2.7 /home/isucon/.local/python/bin/gunicorn -c gunicorn_config.py app:app …
再度ベンチマーカーを実行します。
[isucon@localhost ~]$ ./benchmarker b 10:06:04 type:info message:launch benchmarker 10:06:04 type:warning message:Result not sent to server because API key is not set 10:06:04 type:info message:init environment 10:06:07 type:info message:run benchmark workload: 1 10:07:07 type:info message:finish benchmark workload: 1 10:07:12 type:info message:check banned ips and locked users report 10:07:36 type:report count:banned ips value:1 10:07:36 type:report count:locked users value:2554 10:07:36 type:info message:Result not sent to server because API key is not set 10:07:36 type:score success:5820 fail:0 score:1258
それっぽいスコアになりました!
何もチューニングしないときのスコアは
success:5820 fail:0 score:1258
です。
8.ブラウザからいすこん銀行を表示させる
ベンチマーカーも無事動いたので、ブラウザから画面を表示してみます。
Vagrantfileのポートフォワーディング行のコメントアウトを消して有効にして、hostを8080→3333にします。
config.vm.network "forwarded_port", guest: 80, host: 3333
Vagrantファイルを更新したので再読み込みします。
vagrant reload
これによってホストの特定ポートへのアクセスが、ゲストに転送されます。
ここではホストの3333番ポート宛のアクセスが、仮想マシンの80番ポートに転送されます。
無事表示されました!
9.再プロビジョニングによって設定が上書きされないようにしておく
再度vagrant upしたら、これまで変更した内容が消えてしまうので、
Vagrantfileの下記行を念のためコメントアウトしておきます。
config.vm.provision "shell", inline: <<-SHELL set -e yum install -y epel-release git yum install -y ansible rm -rf ansible-isucon git clone https://github.com/matsuu/ansible-isucon.git ( cd ansible-isucon/isucon4-qualifier PYTHONUNBUFFERED=1 ANSIBLE_FORCE_COLOR=true ansible-playbook playbook.yml -i local ) rm -rf ansible-isucon SHELL
Object.assignのパフォーマンス
掲題の通り、Object.assignのパフォーマンスについて計測してみました。
Object.assignとは
ECMAScript2015の仕様書
ECMAScript 2015 Language Specification – ECMA-262 6th Edition
mixinメソッド。
ソースオブジェクトから列挙可能かつ直接所有のプロパティをターゲットオブジェクトにコピーします。
Object.assign(target, ...sources)
target
ターゲットオブジェクト
sources
ソースオブジェクト
戻り値
ターゲットオブジェクト
準備
パフォーマンス計測のときに使っているコードに記述していきます。
コンストラクタで使ったケースを想定します。
- ES6のclass形式で普通にconstructorを定義する
- Object.assignを使ってconstructor自身を示すthisオブジェクトとマージする。
'use strict'; const suite = require('./suite'); class Person1 { constructor(name, age, gender) { this.name = name; this.age = age; this.gender = gender; } } class Person2 { constructor(name, age, gender) { Object.assign(this, {name, age, gender}); } } suite.run([ { description: 'Contructor#native', exec() { new Person1('Person', 20, '男性'); } }, { description: 'Constructor#assign', exec() { new Person2('Person', 20, '男性'); } } ]);
結果
Contructor#native x 86,560,002 ops/sec ±1.72% (82 runs sampled) Constructor#assign x 733,613 ops/sec ±1.06% (83 runs sampled) Fastest is Contructor#native
何回かやってみましたが結果は上記とほぼ変わらずだったので、
本当にパフォーマンスが求められるときは前者で一つ一つプロパティ代入を書く。
管理画面など、そこまで求められない場合はObject.assignを使っても良さそうです。
Linuxサーバの現場向きオペレーション
説明もわかりやすく、良記事でした。
プロセスツリーをみる(ps)
fをつけるとプロセスの親子関係を把握しやすい
これは地味にうれしい。
top
- cをつけると、プロセスリスト欄に表示されるプロセス名が引数の情報も入ります。
同じProgram nameが複数表示されるときにありがたい。
package.jsonのdependenciesにローカルファイルを指定する
依存するプロジェクトを同時に修正している場合どうしていますか?
自分はこれまでは使いたいプロジェクト側(Aとします)のpackage.jsonに、対象プロジェクト(Bとします)のパスを直接書いていました。
{ … "dependencies": { "test": "../workspace/test" } … }
しかしこれだとBを修正するたびに、Aでnpm installして格納したnode_modules配下のBをいちいち更新しなければならなかったので微妙だなぁと思っていました。
なので下記方法に変更しました。
まずpackage.jsonを修正します。バージョンを直接指定します。
{ … "dependencies": { "test": "1.0.0" } … }
その後に、npm linkコマンドでシンボリックリンクを貼ります。
$ npm link ../workspace/test $ ls -la node_modules/test lrwxr-xr-x 1 hoge fuga Users 49 2 5 15:05 node_modules/test/ -> ../workspace/test $ npm install
シンボリックリンクなのでBに対する修正はそのまま反映されます。
package.jsonはそのままでした。
{ … "dependencies": { "test": "1.0.0" }, … }
これでおーけーね♡
サーバがなんか重い…そんなときの調査の流れ
Web開発の基礎という本を読んだ。
とても良くまとまっている中で、サーバが重いときの調査の流れは改めて復習しておきたい部分だった。
状態:なんか重い!
- 低い:パケットロスなどNWを疑う
netstat -i
or
スイッチのポートのモニタリング
- 高い:CPU使用率を確認
sar -u
-
- %userが高い
CPUを浪費しているユーザ区間のプロセスを探す
top, ps
-
- %sysytemが高い
I/O待ちか確認
sar -u
-
- %iowaitが高い:I/O待ちのプロセスを探す
top, ps
-
- %systemも%userも低い:I/O待ちか確認
sar -u
-
- %iowaitが高い:スワップが発生していないか確認する
sar -W(スワップイン,アウトの状況確認), free(メモリ使用率順でソートする), top(メモリ使用率順でソートする)