SEEDS Creator's Blog

読者です 読者をやめる 読者になる 読者になる

#isucon2にていいかんじにスピードアップできなかった話

ずいぶん遅くなってしまいましたが・・・。

なんでもありの「いい感じにスピードアップコンテスト」、isucon2に京都スイーツとして京都から参加しました。 僕は去年のisuconにも参加したのでこれで2回目となります。

メンバーと担当は以下の通り

kirishima氏 (DBまわりや全体の管理) uchiyama氏 (プログラム周り) 僕 haraguchi (ミドルウェア、インフラ周りの役割。)

前日までにやっていた事

僕は前isucon環境にてnginx + SSI + memcachedをおさらい。 ネットワーク関係のチューニングとか設定ファイルとかを準備。 当日にやる作業とかをまとめていた。 (このメモファイルの抜粋は下の方に追記しました)

メンバーと対策を話した内容

・前回はPOSTが少なかったからかなりPOSTの多い出題になるんじゃないだろうか ・それでもnginx + SSI + memcachedはどこかで使えるんじゃね? ・Redis使おうか・・! → 誰も使った事ないからやめとこ ・前回は全員が一斉に思い思いのチューニングしてfailの原因がわからんくなってしまったから確実に計測していこう ・gitで管理 ・IRC使う ・余裕があればmysql5.6にしたいね

前回のisuconではMySQLのHandlerSocket使おうぜ!とかvarnishに熱中していた、とか ジョブキューするworkerをプログラマが自作して持ち込んだり。 僕たちにとっては挑戦と言える準備をしていたのですが なにがなんだかわからないままFailで終了したという痛い経験がありました。 今回は着実にボトルネックをつぶしていこう、と心に刻んでの参加。

でもこういった熱くなる準備をしなかったのは失敗だったな、、とも思っています。 だって業務ではなかなかできないんだもの・・・醍醐味じゃない。

お題

リバースプロキシサーバ、APPサーバ(*2)、DBサーバー の3台構成。 NHN48とはだいろクローバーZなるアイドルグループのコンサートチケットを販売するサイト。 チケット購入が大量にリクエストされるのでなんとかさばかないと・・・! というチューニング。

11:00くらい

スタート時点全サーバーで鍵設定して手元のmacから「rev02」とかだけでログインできるように設定。 前回のisuconでは無線の調子が不安定で「鍵設定は必須・・・っ!」って思ってたんだけど 今回のisucon2では無線の状態がビンビンで一度も接続が切れなかった。 んで結局、繋ぎなおす事は1回もなかった。無駄だった。

DBのダンプとか一応プログラムのソースのバックアップを行ったりしている間に gitいれてリポジトリ作成。(uchiyama) ボトルネックを調査する為にも各サーバーにいろいろな計測ツールを入れたりする。(kirishima)

そんな感じで初回ベンチを走らせてもらいボトルネックの調査を開始。

~12:00

rev以外のサーバーは全体的に負荷が高かったように思えたけどDBがやっぱりひどい。 でもスローログが出てない。 1秒じゃでないので0.5秒、0.3,0.2と減らしていき、0.1秒くらいで出るようになった。 このあたりのSQL改善に kirishima氏 や uchiyama氏 が当たる。

SQL改善の間に勝手にチューニングしちゃうとまた前回の二の舞になるので我慢して調査に専念する。 全体のインフラ周りの設定確認や、ミドルウェアの設定をサーバー上で作って差し替えれる状態にしたりの作業。 その他スコアに影響与えない範囲でnginxやmemcachedとかを野良ビルドだけしとく。 インフラ周りは ・ローカルポートの使用範囲は多くなってたり (/etc/sysctl.conf) ・SElinuxはOFFだったり(これはちょっと問題あったらしいですがw) ・ip_conntrack_maxも5万くらいに設定されていた。 そうそうボトルネックになりそうなところはないように見えた。

この時、気づいたらチケット取得が順番に並んでて笑ったw ORDER BY RAND()を外してもFailにならなかったそうな。 誰がやったのかなー?

~14:00

SQL周りの修正を行う事でスコアが伸びていく。チケットがたぶん1100枚くらい。でもそこから伸び悩む。 ここで本来なら腰を据えて次のボトルネックを探すべきだったのだが、 「とりあえずmemcacheでキャッシュするか」と、プログラム修正に入る。 思えばここが一番の失敗だったなーと反省・・・。

プログラム修正中にApache、ネットワーク関係、nginxに変えてみるとかの作業とベンチをしてた。

・ネットワーク関係のチューニングを行う  → ticketが100くらい下がる

Apache  → workerにしてみたりリミットを増やしたり  → ticketが100くらい下がる

・nginxに変えてみる  → ticketが800くらい下がる  → これは罠に違いない!って事で一瞬で使われる事がなくなった。(orz)

・静的なファイルはrevサーバーで返す  → ticketが100くらい下がる

どれも惨敗な結果だったのでApache preforkに戻して同時接続数をしぼると 一番パフォーマンスがよかったのでそのように設定。 ここの部分はすごく納得がいかなくて最後までもんもんとしてました。

他の方のブログを見ていると恐らくDBサーバーがデッドロック起こしててrevサーバーががんばっちゃうほど ベンチ結果が悪くなっていったって事なんだろうか。 でも静的ファイルをそのまま返すとスコアがあがるとの事だったけど僕の環境では下がってた。。。 もしかしたらpostが減っただけでgetの項目は増えてたのかなぁ。そこまで見てなかったなぁ。 まぁまぁ・・これは一人isuconで楽しみな内容の一つ。

こんな感じに僕がもんもんとしてる間 uchiyamaがばりばりとプログラムを書いていて kirishimaのDB周りのチューニングをばりばりやってました。 詳細は彼らがブログで語ってくれるのではないかと。

そういえばこのあたりで、プログラムの修正を行うと2台あるappを同期してsuperviserdを再起動しないといけないので rsyncで2台のAppサーバーの同期とsuperviserdを再起動するようなシェルスクリプトを作成したりしてたのを思い出しました。 つまり基本的に小間使い的な立ち位置。

~16:00

左メニューやスゲー数のテーブルが並んだチケット取得状況の部分をmemcachedにつっこむ作業が完了。 でもベンチ結果は変わらず。逆に下がり気味。 ストレージエンジンをmemcacheにしたりIndex張ったり さらに改修してたところでfailばかりになってしまってプチパニック。 このあたりくらいからIRCがまったく使われなくなってくるw

~18:00

failで終了はイヤダー! って事でベンチが完走した状態までgitで戻す。DB関係もdumpから元に戻す。 そこからベンチが完走する事を確認してmemcachedにつっこめていなかった部分をちょこちこ改修。 DB関係のチューニングも再試行。 とりあえずはちゃんと動く状態にて終了。 結果的にはSQLの改修くらいしかスコアに貢献できてないという散々な結果に。

それでもよかった、楽しかった

結果的にはあまりチューニングできなかったけど 「ちゃんと動くもの」として結果を残せたのはよかったとは思ってます。 うまくいかない事もあり悔しかったけどisuconは本当に楽しいです。 準備して、isuconに挑んで、一人isuconして納得できるチューニング!までがisuconだと思ってます。

今回の大きな反省は、3人で「確実にボトルネックをつぶしていこう!」という話をしていたのに 安易にmemcacheにつっこむ改修をしちゃったところかな、、と。 経験の差もあるとは思うんですが、「確実にボトルネックをつぶす」という作業が とても基本的なのにいかに難しい事か・・・思い知らされました。

懇親会もみなさんがすごい人ばかりで話をする機会なんかなかなか持てない方々ばかり。 とてもいい刺激になりました。

最後になりましたが運営の方々は本当にお疲れ様でした。 とても楽しい時間を過ごせて感謝でいっぱいです。 また、今回もisucon参加に会社から京都⇔東京の旅費や宿泊費を頂きました。 これだけ支援してくれる会社はなかなかないのでしょうか。とても感謝です。

次があるならまた参加したいです。(心から!)

せっかくなので・・・

僕がisuconの準備で用意してたテキストファイルをのせます。 ほんとにメモ程度のもので信憑性も詳細な説明もありませんが。

鍵作成

[code] ssh-keygen -t dsa -N "" -f /root/.ssh/rev02 /root/.ssh/authorized_keys にpubを置く [/code]

telnetいるかも

CentOS6にはtelnetが入っていないかもしれない。 memcacheのステータスを見るのに必要かもしれないからない場合は面食らわないようにー

[code] yum -y install telnet [/code]

いらないサービスをストップ

[code] chkconfig auditd off chkconfig autofs off chkconfig avahi-daemon off chkconfig bluetooth off chkconfig cups off chkconfig firstboot off chkconfig gpm off chkconfig haldaemon off chkconfig hidd off chkconfig isdn off chkconfig kudzu off chkconfig lvm2-monitor off chkconfig mcstrans off chkconfig mdmonitor off chkconfig messagebus off chkconfig netfs off chkconfig nfslock off chkconfig pcscd off chkconfig portmap off chkconfig rawdevices off chkconfig restorecond off chkconfig rpcgssd off chkconfig rpcidmapd off chkconfig smartd off chkconfig xfs off chkconfig yum-updatesd off [/code]

コンソールこんなにいらないよ

/etc/inittab

[code] 2:2345:respawn:/sbin/mingetty tty2 3:2345:respawn:/sbin/mingetty tty3 4:2345:respawn:/sbin/mingetty tty4 5:2345:respawn:/sbin/mingetty tty5 6:2345:respawn:/sbin/mingetty tty6 [/code]

本番ではきれいに1個だけだった。

selinuxを無効

[code] setenforce 0 [/code]

vi /etc/sysconfig/selinux [code] SELINUX=enforcing ↓ SELINUX=disabled [/code]

ネットワーク関係のチューニング

リバースプロキシサーバーならこれくらいの設定をしちゃえばネットワーク関係の制限でボトルネックとはなりにくいだろう・・・ という設定

vi /etc/sysctl.conf [code] net.ipv4.ip_local_port_range = 10240 65000 net.core.rmem_max=16777216 net.core.wmem_max=16777216 net.ipv4.tcp_rmem=4096 87380 16777216 net.ipv4.tcp_wmem=4096 65536 16777216 net.ipv4.tcp_tw_recycle = 1 net.ipv4.tcp_fin_timeout = 3 net.core.netdev_max_backlog = 30000 net.ipv4.tcp_no_metrics_save=1 net.core.somaxconn = 262144 net.ipv4.tcp_syncookies = 0 net.ipv4.tcp_max_orphans = 262144 net.ipv4.tcp_max_syn_backlog = 262144 net.ipv4.tcp_synack_retries = 2 net.ipv4.tcp_syn_retries = 2 net.ipv4.tcp_max_tw_buckets = 56384 [/code]

[code] sysctl -w sysctl -p [/code]

iptablesを消す

ip_conntrack_maxがひっかかる場合。

[code] /etc/init.d/iptables stop chkconfig iptables off chkconfig iptables --list iptables -F [/code]

memcache関連

yum install libevent-devel [code] cd /usr/local/src wget http://memcached.googlecode.com/files/memcached-1.4.15.tar.gz tar zxvf memcached-1.4.10.tar.gz cd memcached-1.4.10 ./configure --enable-64bit make make install [/code]

中に入ってるキーを一覧するperlワンライナー [code] perl -MCache::Memcached -e '$s="localhost:11211";$m=new Cache::Memcached({servers=>[$s]});$res=$m->stats("items");$i=$res->{hosts}{$s}{items};@a=split("€n",$i);while(){if($=~/items:([0-9]+)/){$s{$1}=$}};foreach $key (keys %s){$cm="cachedump $key 100";$res=$m->stats($cm);print "--- €n".$cm."€n";print $res->{hosts}{$s}{$cm}}' [/code]

再起動時にキャッシュに突っ込むシェルスクリプト

アプリの構成によるけどこんな感じにしておけばキャッシュをつっこんでおけるかも?

[code] !/bin/sh

for i in seq 1 1 3000 do wget http://app01/article/$i -O - done [/code]

nginx関連

[code] wget http://nginx.org/download/nginx-1.3.8.tar.gz ./configure make make install [/code]

nginx.confのひながた

[code] worker_processes 4; user root;

events { worker_connections 10000; }

http { #access_log /dev/null; error_log /dev/null; include mime.types; default_type text/html; open_file_cache max=10000; sendfile on; keepalive_timeout 0;

    upstream hoge {
        server xxx.xxx.xxx.xxx:5000;
        server xxx.xxx.xxx.xxx:5000;
        keepalive 1000000;
    }

    upstream memcached {
        server 127.0.0.1:11212;
        keepalive 1000000;
    }

    server {
        listen       80;
        server_name  127.0.0.1;

        #静的ファイルはrevサーバーに移動しておく
        location /images {
            root /path/to/staticfiles;
        }

        #特定のURLのみキャッシュをコントロールする場合 (SSI使って切り分けた先のキャッシュ)
        location /left_menu {
            set $memcached_key "/left_menu";
            memcached_pass memcached;
            default_type text/html;
            error_page         404 502 = @fallback;
        }

        location / {
            if ($request_method = POST) {
                proxy_pass http://hoge;
                break;
            }
            ssi on;
            ssi_silent_errors on;
            set $memcached_key $uri;
            memcached_pass     memcached;
            default_type       text/html;
            error_page 404 =200 @fallback;
            error_page 302 =302 @fallback;
            error_page 502 =502 @fallback;
        }

        location @fallback {
            proxy_pass         http://hoge;
        }
    }

} [/code]

mysql関連

とりあえずダンプとっておく [code] mysqldump -pXXXXX -u root -x -A > /home/my.dump [/code]

スロークエリの設定を動かしながら行う。 mysqlにログインして以下のような感じで [code] set global slow_query_log = 1; set global slow_query_log_file = '/path/to/slow.log'; set global long_query_time = 1; [/code]

mysql5.6を入れる事になる場合・・・ [code] wget http://dev.mysql.com/get/Downloads/MySQL-5.6/mysql-5.6.7-rc-linux2.6-x86_64.tar.gz/from/http://cdn.mysql.com/ wget http://downloads.mysql.com/snapshots/pb/mysql-5.6-labs-innodb-memcached/mysql-5.6.4-labs-innodb-memcached.tar.gz [/code]

設定用のsqlを流し込む

[code] mysql < scripts/innodb_memcached_config.sql [/code]

mysqlにログインしてmemcacheを動かす。 11211で立ち上がるので元のmemcacheはポートかえとくべき。 [code] install plugin daemon_memcached soname "libmemcached.so"; show plugins; [/code]