hero_picture

#isucon 4 予選に参加しました(スコア 37513)

2014/10/03

@memememomo (uchiko) と onihsiと@cs_sonar(僕)で参加しました。

チーム名は「京都スイーツ」です。

結果としては本戦出場はできそうにないスコアで残念でした・・・。

(2014/10/06 追記。失格になってました。)

以下備忘録です。

インスタンス立ち上げ

AMI-IDをゲットしてインスタンスの立ち上げ。

全員にそれぞれ検証環境を用意する事を前日話していたのでインスタンス台数は4で一気に立ち上げ。

一つを本番用、他3つをメンバー用。

perlに切り替え

superviserd stopしてもrubyのアプリが立ち上がったままだったので

なんでやと思いながらも躊躇なくkill -killした。

無事perlで立ち上がってベンチ実行してスタートダッシュ成功。

一瞬でも1位がとれて、「優勝したあ!isucon優勝!」とあほみたいに騒ぐ。

ベンチを回すコマンド作成

ベンチマークツールの実行コマンド(特にapiキーあり)がいろいろ面倒なので

1/bin/ben (ベンチテスト用)
2/bin/benben (ベンチ+スコア送信用)
3

という名のシェルスクリプトを作る。

benben でスコア報告ベンチが走るのでちょっと楽。

ただ、これにより、「べんべんいく?」「とりあえずべんしよう」とかはたから見たら異様な会話に。

git(bitbucket)を使う

gitはbitbucketのプライベートリポジトリを使った。

このあたりの設定をmemememomoがやってくれた。

vim

memememomoが秘伝のvim設定を投入。

ログ解析

アクセスのパターンを確認できればいいかと簡易のアクセス解析ツールを使った。

前日にぐぐって見つけたvisitorsというもの。

1cd /usr/local/src
2wget http://www.hping.org/visitors/visitors-0.7.tar.gz
3tar zxvf visitors-0.7.tar.gz
4make
5
1./visitors -A -o text /var/log/nginx/access_log
2* Different pages requested: 4
31)    /: 4160
42)    /login: 2284
53)    /mypage: 440
64)    /report: 6
7

え、urlこんだけ!?

nginxの設定

nginxの設定が必要最低限だったので修正。

ついでに静的ファイルはnginxから返すようにやっておいた。

1worker_processes 8;
2events {
3worker_connections  10000;
4}
5http {
6sendfile        on;
7include       mime.types;
8upstream app {
9server 127.0.0.1:8080;
10keepalive 1000000;
11}
12server {
13location /images {
14alias /home/isucon/webapp/public/images;
15}
16location /stylesheets {
17alias /home/isucon/webapp/public/stylesheets;
18}
19location = /favicon.ico {
20log_not_found off;
21alias /home/isucon/webapp/public/favicon.ico;
22}
23location / {
24proxy_pass http://app;
25}
26}
27}
28

mysqlの設定

mysqlも必要最低限だったので設定

1[mysqld]
2datadir=/var/lib/mysql
3socket=/var/lib/mysql/mysql.sock
4symbolic-links=0
5max_allowed_packet=300M
6skip-name-resolve
7innodb_flush_log_at_trx_commit = 0
8innodb_additional_mem_pool_size=40M
9innodb_log_buffer_size=32M
10innodb_log_file_size=256M
11innodb_buffer_pool_size=3600M
12max_connections = 2048
13max_connect_errors = 10000
14tmp_table_size=134217728
15max_heap_table_size = 134217728
16key_buffer_size = 512M
17table_cache=2048
18sort_buffer_size = 1M
19read_buffer_size = 1M
20read_rnd_buffer_size = 4M
21myisam_sort_buffer_size = 1M
22thread_cache_size = 128
23thread_concurrency = 8
24[mysqld_safe]
25log-error=/var/log/mysqld.log
26pid-file=/var/run/mysqld/mysqld.pid
27

ネットワークやサーバー制限まわりの設定

このあたりでつまずくのは時間が勿体ないので、あらかじめ上限をあげておきました。

(ですので、ローカルポート枯渇などではひっかかってないです)

iptablesをOFF

ipconntracとか出て時間をくいたくないので念のため。

1/etc/init.d/iptables stop
2chkconfig iptables off
3chkconfig iptables --list
4iptables -F
5

sysctl.conf の設定

この設定はvarnishの公式に「varnish使うならこんな設定まじおすすめ」みたいに紹介されてたもの。

1net.ipv4.ip_local_port_range = 10240 65000
2net.core.rmem_max=16777216
3net.core.wmem_max=16777216
4net.ipv4.tcp_rmem=4096 87380 16777216
5net.ipv4.tcp_wmem=4096 65536 16777216
6net.ipv4.tcp_tw_recycle = 1
7net.ipv4.tcp_fin_timeout = 3
8net.core.netdev_max_backlog = 30000
9net.ipv4.tcp_no_metrics_save=1
10net.core.somaxconn = 262144
11net.ipv4.tcp_syncookies = 0
12net.ipv4.tcp_max_orphans = 262144
13net.ipv4.tcp_max_syn_backlog = 262144
14net.ipv4.tcp_synack_retries = 2
15net.ipv4.tcp_syn_retries = 2
16net.ipv4.tcp_max_tw_buckets = 56384
17

ファイルディスクリプタでも時間をくいたくないのでlimits.confを設定。

1*               hard    nofile             65535
2*               soft    nofile             65535
3

ここまでやってみてスコアは2500とあまりかわらず。

MySQLボトルネックなのはわかっていたのでボトルネックがうつった時に効果を発揮してくれるでしょう

インデックスを張る

onihsiがインデックスを張ってくれました

1ALTER TABLE `isu4_qualifier`.`login_log`
2ADD INDEX `ip` (`ip` ASC),
3ADD INDEX `login` (`login` ASC),
4ADD INDEX `user_id-succeeded` (`user_id` ASC, `succeeded` ASC),
5ADD INDEX `ip-succeeded` (`ip` ASC, `succeeded` ASC),
6ADD INDEX `succeede-user_id` (`succeeded` ASC, `user_id` ASC);
7

これと上記のインフラ周りの修正でスコアは20000くらいになった。

banのユーザをredisでカウントするようにした

ipアドレス と ユーザー名を各々キーにしてログイン失敗で

1    $self->redis->incr($ip);
2$self->redis->incr($user->{id});
3

みたくインクリメント

ログイン成功で

1    $self->redis->set($user->{id}, 0);
2$self->redis->set($ip, 0);
3

ゼロに戻す

これでip_bunnedの確認とかが

1sub ip_banned {
2my ($self, $ip) = @_;
3return undef unless $ip;
4my $count = $self->redis->get($ip) || 0;
5return $self->config->{ip_ban_threshold} <= $count;
6};
7

となる

でも/reportsの修正は怖かったのでlogin_logへのINSERTはそのまま。

こんな修正をmemememomoがばばーとやってくれた。

でも24000。あんまりのびなくてあるぇえええ?ってなる。

セッションの管理をメモリで行うようにした

@memememomoがベンチ回すと「max Open files」的なエラーが出るというので見てみると

/tmp内にあるセッションファイルがものごっつい量になっていた。

これ以上ファイルが書き込めないという状態になっていて1ディレクトリの上限ファイル数でも叩いてたのかな。(あんまり調べてない)

こんだけファイルができるくらいだからセッション書き込みにおけるディスクIOも問題となっているに違いない事に気づいて

memememomoにセッションをメモリで管理するように変更してもらった。

1Plack::Session::Store::Cache
2Cache::FastMmap
3CHI
4

redis使うよりもこっちの修正のほうが効いて

たぶんここらへんで32000

いや、まぁここがボトルネックで解消した結果redis修正が生きた、という可能性もありますが。

平文のパスワード

このときベンチではもうほとんどがアプリの負荷だったのでどこかアプリで重いとこないかを探した。

プロファイルしようかともmemememomoが悩んでたんですが

calculate_password_hashって明らかに重いだろ、、、という事でここをなんとかする事に。

元のinitialのsqlデータやtsvデータには元passwordが平文で書かれてたのでこれを入れちゃえば

calculate_password_hash使わなくてよくなる。

usersテーブルをコピーしてusers_pwを作成。

ここのpassword_hashに平文のパスワードを入れて、平文のパスワードで認証するようにした。

これでなんか重そうな雰囲気のcalculate_password_hashを呼ばなくてよくなった。

終わり

ベンチ中topをなにげなく眺めてると、memememomoの秘伝のvimがCPU20~30%も消費してる事を発見。

vimを落としたベンチで

Score 37513 の最高スコアを送信してend

無駄だったこと1

/のエラー文言部分だけを別URLにしてindex.txからSSIで読むようにした。

これにより/をnginxでキャッシュできたんだけど、速くなるわけなかった。

ssi処理のぶん遅くなっただけで意味がなくて元に戻した。

なんでこれで早くなると思ったのか思い返したら謎。

無駄だったこと2

ログイン失敗数等を別テーブルで持たせてSQLの負荷軽減をした。

Redisでカウントする事にしたのでこの修正も無駄になった。

やりたかったこと1

すべてのデータをredisに入れてmysqlと決別したかった。

でも過去の経験から普段やったことない作業をやると泥沼化が見えてるのでやらない事にした。

具体的にはreportsの部分に手を入れるのが怖かったのと、redis力が不安だった

反省会

反省会の焼肉屋でTOPページのindexをエラー文言別に静的に用意しておいてnginxがCookieで振り分けると早くなりそうという案が出た。

他の人のブログでもそんな改修があって、これについては気づきが足りなかったなぁと悔しい思いに。

実際この作業をやっても43000点くらいまでしか伸びなかったのですが、

これをやってたら本戦にもしかしたら出れたかもと思うとうぎゃーとなりました。

最後に

最後になりましたが、isuconには今回でもう4回も参加させていただいており、

企画/運営/協賛されておりますLINE/cookpad/DATAHOTEL/Amazonの皆様にはほんとうに感謝しております。

ありがとうございます!

本戦出場者が決定しました!

http://isucon.net/archives/40576269.html

京都スイーツはギリギリだめでした。。。と思って読み進めると、失格してました。

1・「京都スイーツ」チームは、 /mypage にログインユーザ名が表示されていないため、表示崩れとみなし、失格といたしました。
2

調べてみると SELECT * を書き換える時に必要なカラムが漏れてた模様。

どちらにしてもスコア足りてなかったので結果は同じですが失格は悲しい。

一応チェックされるほどの上位にいけたということで許して下さい、、、と会社に言い訳。

ここまでチェックするって運営さんはすごく大変だったろうな、、、と改めて感謝の気持ちでいっぱいです。

今年も楽しい問題、ありがとうございました。