bauer's diary

凡人の凡人による凡人のための備忘録

きたる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
MySQL設定再読み込み
$ sudo service mysqld reload

反映完了。


次回はNginxのチューニングを行います。
次回記事はこちら

きたるISUCON6向け武者修行その1 -仮想環境を構築して負荷試験を実行してみよう-

強い意志の元、今年こそISUCONに参加しようと思います。

ISUCONとは?

お題となるWebサービスを決められたレギュレーションの中で限界まで高速化を図るチューニングバトル、それがISUCON

isucon.net

そこで、まずはチームメンバーと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.ローカルにVirtualBoxVagrantをインストール

ここでは手軽にbrew caskでインストールします。

$ brew cask install virtualbox
$ brew cask install vagrant

VirtualBoxは、仮想的なPCを作成し、特定のOSをインストール・実行できるソフトウェアです。
Vagrantは、仮想環境の構築と制御を行うソフトウェアです。

検証環境は下記の通りです。


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番ポートに転送されます。

f:id:kitakitabauer:20160713171530p:plain

無事表示されました!

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
 ソースオブジェクト
戻り値
 ターゲットオブジェクト

準備

github.com

パフォーマンス計測のときに使っているコードに記述していきます。
コンストラクタで使ったケースを想定します。

  1. ES6のclass形式で普通にconstructorを定義する
  2. 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サーバの現場向きオペレーション

yuuki.hatenablog.com

説明もわかりやすく、良記事でした。

プロセスツリーをみる(ps)

fをつけるとプロセスの親子関係を把握しやすい

これは地味にうれしい。

NICIPアドレスの確認(ip)

いまだifconfigを使っていたので、ipのほうが俄然見やすい。

top
  • cをつけると、プロセスリスト欄に表示されるプロセス名が引数の情報も入ります。

同じProgram nameが複数表示されるときにありがたい。

nestat / ss

netstatも非推奨なので、ssコマンドを使ったほうがよいとのこと。

ただし、自分の場合、ss コマンドの出力の余白の取り方があまり好きではないので、netstat をつかっています。

たしかに余白が多かったので自分もnetstatかなぁ。

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"
  },
  …
}

これでおーけーね♡

Nodeのバージョンアップに伴うグローバルパッケージの移行

Nodeのバージョンを上げるときに、旧バージョンでグローバルインストールしたパッケージを移行できます。
指定するX.X.Xはそれぞれ移行元version。

nodebrewの場合
$ nodebrew migrate-package vX.X.X

グローバルにインストールされたパッケージを、カレントバージョンにも適用する。

npmの場合
$ nvm install v4.X.X --reinstall-package-from=X.X.X

nvmはinstallオプションしかないのか…

サーバがなんか重い…そんなときの調査の流れ

Web開発の基礎という本を読んだ。

とても良くまとまっている中で、サーバが重いときの調査の流れは改めて復習しておきたい部分だった。

状態:なんか重い!

ロードアベレージを確認
uptime

  • 低い:パケットロスなどNWを疑う

netstat -i
or
スイッチのポートのモニタリング

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(メモリ使用率順でソートする)