Skip to content

Instantly share code, notes, and snippets.

@minimum2scp
Last active August 29, 2015 14:06
Show Gist options
  • Select an option

  • Save minimum2scp/8e42b76b7a1cd2eb038c to your computer and use it in GitHub Desktop.

Select an option

Save minimum2scp/8e42b76b7a1cd2eb038c to your computer and use it in GitHub Desktop.
第8回 ITインフラ勉強会@福岡 (http://fukinfra.doorkeeper.jp/events/14234) でやったチューニングのメモ。「ISUCONで学ぶ Webアプリケーションのパフォーマンス向上のコツ 実践編 完全版」 http://blog.nomadscafe.jp/2014/08/isucon-web-apps-tune.html を実践してみた。
やりたいこと
* [x] etckeeper (/etc を git でバージョン管理)
* [×] ~/isucon 以下を git でバージョン管理
* [x] ruby 2.1.2 のリビルド (libgmp つきで高速化)
## etckeeper
sudo yum install --enablerepo=epel etckeeper
sudo etckeeper init
sudo etckeeper commit "Initial commit"
以降は、
* sudo etckeeper commit "commit message"
* sudo etckeeper vcs status
* sudo etckeeper vcs diff
* sudo etckeeper vcs log
とかできます
## ruby 2.1.2 のリビルド
[isu-user@ip-10-126-167-190 ~]$ ldd ~/local/ruby-2.1.2/bin/ruby
linux-vdso.so.1 => (0x00007fff6d9f6000)
libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f322b542000)
libdl.so.2 => /lib64/libdl.so.2 (0x00007f322b33d000)
libcrypt.so.1 => /lib64/libcrypt.so.1 (0x00007f322b107000)
libm.so.6 => /lib64/libm.so.6 (0x00007f322ae09000)
libc.so.6 => /lib64/libc.so.6 (0x00007f322aa63000)
/lib64/ld-linux-x86-64.so.2 (0x00007f322bc53000)
libfreebl3.so => /lib64/libfreebl3.so (0x00007f322a7e6000)
なので、gmp-devel 入れてリビルドします
(IntegerかFloatかが速くなるはず)
sudo yum install gmp-devel
mkdir ~/tmp
cd ~/tmp
curl -LO http://cache.ruby-lang.org/pub/ruby/2.1/ruby-2.1.2.tar.gz
tar xf ruby-2.1.2.tar.gz
cd ruby-2.1.2
./configure --prefix=$HOME/local/ruby-2.1.2+gmp
make -j4
make install
## 使用する ruby の切り替えと gem のインストール
~/.bashrc
export PATH=/home/isu-user/local/ruby-2.1.2/bin:$PATH
export PATH=/home/isu-user/local/ruby-2.1.2+gmp/bin:$PATH
に書き換え
変更を反映するため再ログイン後、
gem install -N bundler
アプリにも反映するため、~/isucon/env.sh を書き換え
commit c507039410992b5561dea1fcaef3b5209c3e9bf6
Author: Your Name <you@example.com>
Date: Sat Sep 13 05:31:10 2014 +0000
env.sh: changed ruby interpreter (gmp enabled version)
diff --git a/env.sh b/env.sh
index 003bbd6..aa928fb 100755
--- a/env.sh
+++ b/env.sh
@@ -1,6 +1,7 @@
#!/bin/sh
export PATH=/home/isu-user/local/perl-5.20/bin:$PATH
-export PATH=/home/isu-user/local/ruby-2.1.2/bin:$PATH
+#export PATH=/home/isu-user/local/ruby-2.1.2/bin:$PATH
+export PATH=/home/isu-user/local/ruby-2.1.2+gmp/bin:$PATH
export PATH=/home/isu-user/local/node-v0.10/bin:$PATH
export PATH=/home/isu-user/local/python-3.4.1/bin:$PATH
export PATH=/home/isu-user/local/go/bin:$PATH
もともとの ruby を誤って使わないよう、固めて消しておく
cd ~/local
tar cfz ruby-2.1.2.tar.gz ruby-2.1.2
rm -rf ruby-2.1.2
アプリ再起動
sudo supervisorctl restart isucon_ruby
# benchmark
before 1619
after1 1530 (down)
after2 1580 (down)
# rollback ruby 2.1.2
スコアおちたので戻します
cd ~/local
tar xf ruby-2.1.2.tar.gz
tar cfz ruby-2.1.2+gmp.tar.gz ruby-2.1.2+gmp
rm -rf ruby-2.1.2+gmp
~/.bashrc と ~/isucon/env.sh も巻き戻し(内容略)
プロセス(シェル、アプリ)再起動
# やること(やまだ)
* [x] apache -> nginx
* [x] rewrite
* [x] unicorn プロセス数減らす
* [x] TCP -> unix domain socket
# やること(あづち)
* [x] markdownのコマンド呼び出しをライブラリ化
* [×] mysqlのインデックス作成
# markdownのライブラリ化
Gemfileにgem 'rdiscount'を追加して bundle install --no-deployment
app.rbでrequire 'rdiscount'を追加し、markdown生成するメソッドを以下に修正。
  def gen_markdown(md)
RDiscount.new(md).to_html
end
# benchmark
before 1619
after 2086 (up)
# apache -> nginx
sudo yum install nginx
(1.4.7 古い)
sudo yum install http://nginx.org/packages/centos/6/x86_64/RPMS/nginx-1.6.1-1.el6.ngx.x86_64.rpm
apache の設定をまねて、アプリに ReverseProxy できるようにする
/etc/nginx/conf.d/default.conf を default.conf.disable にして無効化
/etc/nginx/conf.d/isucon.conf:
server {
listen 80;
server_name localhost;
root /home/isu-user/isucon/webapp/public;
#index index.html index.htm;
try_files try_files $uri/index.html $uri.html $uri @app;
location @app {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_pass http://localhost:5000;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
apache を停止して nginx を起動
sudo service httpd stop
sudo service nginx start
# benchmark
score 2086 -> 2153
# MySQLのインデックス作成
init.shに↓追加
cat <<'EOF' | mysql -u isucon isucon
ALTER TABLE memos ADD INDEX (is_private, created_at);
EOF
# benchmark
score 2153.2 -> 2263.8
# rewrite
try_files なのでもうできてる(はず)
# nginx worker ふやす
diff --git a/nginx/nginx.conf b/nginx/nginx.conf
index e4bad8d..a3e54e0 100644
--- a/nginx/nginx.conf
+++ b/nginx/nginx.conf
@@ -1,6 +1,6 @@
user nginx;
-worker_processes 1;
+worker_processes 4;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
sudo service nginx reload
# benchmark
2263.8 -> 2342
# unicorn プロセス数減らす
diff --git a/webapp/ruby/unicorn_config.rb b/webapp/ruby/unicorn_config.rb
index 9f6d110..628679e 100644
--- a/webapp/ruby/unicorn_config.rb
+++ b/webapp/ruby/unicorn_config.rb
@@ -1,2 +1,2 @@
-worker_processes 10
+worker_processes 4
preload_app true
# benchmark
2342 -> 2242
SQL とかのチューニングが十分効くまでは、worker_processは戻しておく(4->10)
2242 -> 2200
(設定戻したのにスコアは戻らない…)
# TCP -> unix domain socket
nginx:
diff --git a/nginx/conf.d/isucon.conf b/nginx/conf.d/isucon.conf
index 95125ef..2e2cfa0 100644
--- a/nginx/conf.d/isucon.conf
+++ b/nginx/conf.d/isucon.conf
@@ -10,7 +10,8 @@ server {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
- proxy_pass http://localhost:5000;
+ #proxy_pass http://localhost:5000;
+ proxy_pass http://unix:/tmp/unicorn.sock;
}
error_page 500 502 503 504 /50x.html;
unicorn:
diff --git a/webapp/ruby/unicorn_config.rb b/webapp/ruby/unicorn_config.rb
index 9f6d110..559273a 100644
--- a/webapp/ruby/unicorn_config.rb
+++ b/webapp/ruby/unicorn_config.rb
@@ -1,2 +1,4 @@
worker_processes 10
preload_app true
+listen "/tmp/unicorn.sock"
+
Procfile (foreman):
diff --git a/webapp/ruby/Procfile b/webapp/ruby/Procfile
index a17068e..32a2d12 100644
--- a/webapp/ruby/Procfile
+++ b/webapp/ruby/Procfile
@@ -1 +1,2 @@
-web: bundle exec unicorn -c unicorn_config.rb -p $PORT
+#web: bundle exec unicorn -c unicorn_config.rb -p $PORT
+web: bundle exec unicorn -c unicorn_config.rb
# benchmark
2200 -> 2214
# newrelic
Gemfile に 'newrelic_rpm' を追加、
newrelic.yml を追加、
config.ru に require 'newrelic_rpm' を追加
bundle してアプリ再起動
# benchmark
2143.2
# nrsysmond
sudo yum install http://download.newrelic.com/pub/newrelic/el5/i386/newrelic-repo-5-3.noarch.rpm
sudo yum install newrelic-sysmond
sudo nrsysmond-config --set license_key=xxxxxxxxxxxxxxxxxxxxxxxxxxxx
sudo /etc/init.d/newrelic-sysmond start
# titleカラムの追加
init.shを以下に修正。
cat <<'EOF' | mysql -u isucon isucon
ALTER TABLE memos ADD INDEX (is_private, created_at);
ALTER TABLE memos ADD COLUMN title text;
UPDATE memos SET title = substring_index(content, "\n", 1);
EOF
app.rbのポスト処理時にtitleを生成。
index.erbのタイトル生成処理をtitleカラムを参照するよう変更。
# benchmark
2143.2 -> 2215.8
# memoのユーザ取得をクエリ発行じゃなくてmemosのJOINで一緒に行うよう修正
app.rbの/と/recent/:pageアクセス時のクエリを
SELECT memos.*, users.username FROM memos INNER JOIN users ON (users.id = memos.user) WHERE is_private=0 ORDER BY memos.created_at DESC, memos.id DESC
に変更し、memos.eachでusersテーブルへ発行してたクエリを削除
# benchmark
2215.8 -> 2479.7
# gen_markdown を memcached に
1回目 2390 くらい
2回目で FAIL した
* MySQL は巻き戻されたけどキャッシュクリアしてなかった
* そもそも同じメモを2回以上 GET されないと意味がない
revert した
# select * してる部分をカラム指定に変更
# benchmark
2479.7 -> 2522.3
# mysql my.cnf チューニング
-innodb_buffer_pool_size = 2GB
+innodb_buffer_pool_size = 8GB
+innodb_flush_log_at_trx_commit = 2
mysql restart.
# benchmark
2522 -> 2594.5
# SQL書き換え (get /recent/:page)
diff --git a/webapp/ruby/app.rb b/webapp/ruby/app.rb
index 15167d5..0191dd7 100644
--- a/webapp/ruby/app.rb
+++ b/webapp/ruby/app.rb
@@ -91,7 +91,11 @@ class Isucon3App < Sinatra::Base
page = params["page"].to_i
total = mysql.xquery('SELECT count(*) AS c FROM memos WHERE is_private=0').first["c"]
- memos = mysql.xquery("SELECT memos.id, memos.title, memos.created_at, users.username FROM memos INNER JOIN users ON (users.id = me
+ memos = mysql.xquery(
+ "SELECT memos.id, memos.title, memos.created_at, users.username FROM memos, users, " +
+ "(SELECT id FROM memos WHERE is_private = 0 ORDER BY created_at DESC, id DESC LIMIT 100 OFFSET #{page * 100}) AS t " +
+ "WHERE t.id = memos.id AND users.id = memos.user"
+ )
if memos.count == 0
halt 404, "404 Not Found"
end
# benchmark
2594.5 -> 3301.2
# SQL書き換え (get /memo/:memo_id)
diff --git a/webapp/ruby/app.rb b/webapp/ruby/app.rb
index 0191dd7..6c62604 100644
--- a/webapp/ruby/app.rb
+++ b/webapp/ruby/app.rb
@@ -177,7 +177,7 @@ class Isucon3App < Sinatra::Base
memos = []
older = nil
newer = nil
- results = mysql.xquery("SELECT * FROM memos WHERE user=? #{cond} ORDER BY created_at", memo["user"])
+ results = mysql.xquery("SELECT id FROM memos WHERE user=? #{cond} ORDER BY created_at", memo["user"])
results.each do |m|
memos.push(m)
end
# benchmark
3301 -> 3539
# unicorn プロセス数 10 -> 4
diff --git a/webapp/ruby/unicorn_config.rb b/webapp/ruby/unicorn_config.rb
index 559273a..a8abd72 100644
--- a/webapp/ruby/unicorn_config.rb
+++ b/webapp/ruby/unicorn_config.rb
@@ -1,4 +1,4 @@
-worker_processes 10
+worker_processes 4
preload_app true
listen "/tmp/unicorn.sock"
# benchmark
3539 -> 3603
# SQL tuning (GET /)
diff --git a/webapp/ruby/app.rb b/webapp/ruby/app.rb
index 6c62604..e0df60d 100644
--- a/webapp/ruby/app.rb
+++ b/webapp/ruby/app.rb
@@ -76,8 +76,12 @@ class Isucon3App < Sinatra::Base
user = get_user
total = mysql.query("SELECT count(*) AS c FROM memos WHERE is_private=0").first["c"]
- memos = mysql.query(" SELECT memos.id, memos.title, memos.created_at, users.username FROM memos INNER JOIN users ON (users.id = m
- erb :index, :layout => :base, :locals => {
+ memos = mysql.xquery(
+ "SELECT memos.id, memos.title, memos.created_at, users.username FROM memos, users, " +
+ "(SELECT id FROM memos WHERE is_private = 0 ORDER BY created_at DESC, id DESC LIMIT 100) AS t " +
+ "WHERE t.id = memos.id AND users.id = memos.user"
+ )
+ erb :index, :layout => :base, :locals => {
:memos => memos,
:page => 0,
:total => total,
# benchmark
3603 -> 3570.2
GET / のチューニングはrevert.
# インデックス追加
ALTER TABLE memos ADD INDEX (is_private, created_at),
ADD INDEX mypage(user, created_at),
ADD INDEX memo_private(user,is_private,created_at);
# benchmark
3570.2 -> 4303.3
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment