#isucon 4 予選に参加しました(スコア 37513)
@memememomo (uchiko) と onihsiと@cs_sonar(僕)で参加しました。
チーム名は「京都スイーツ」です。
結果としては本戦出場はできそうにないスコアで残念でした・・・。
(2014/10/06 追記。失格になってました。)
以下備忘録です。
インスタンス立ち上げ
AMI-IDをゲットしてインスタンスの立ち上げ。
全員にそれぞれ検証環境を用意する事を前日話していたのでインスタンス台数は4で一気に立ち上げ。
一つを本番用、他3つをメンバー用。
perlに切り替え
superviserd stopしてもrubyのアプリが立ち上がったままだったので
なんでやと思いながらも躊躇なくkill -killした。
無事perlで立ち上がってベンチ実行してスタートダッシュ成功。
一瞬でも1位がとれて、「優勝したあ!isucon優勝!」とあほみたいに騒ぐ。
ベンチを回すコマンド作成
ベンチマークツールの実行コマンド(特にapiキーあり)がいろいろ面倒なので
/bin/ben (ベンチテスト用) /bin/benben (ベンチ+スコア送信用)
という名のシェルスクリプトを作る。
benben でスコア報告ベンチが走るのでちょっと楽。
ただ、これにより、「べんべんいく?」「とりあえずべんしよう」とかはたから見たら異様な会話に。
git(bitbucket)を使う
gitはbitbucketのプライベートリポジトリを使った。
このあたりの設定をmemememomoがやってくれた。
vim
memememomoが秘伝のvim設定を投入。
ログ解析
アクセスのパターンを確認できればいいかと簡易のアクセス解析ツールを使った。
前日にぐぐって見つけたvisitorsというもの。
cd /usr/local/src wget http://www.hping.org/visitors/visitors-0.7.tar.gz tar zxvf visitors-0.7.tar.gz make
./visitors -A -o text /var/log/nginx/access_log * Different pages requested: 4 1) /: 4160 2) /login: 2284 3) /mypage: 440 4) /report: 6
え、urlこんだけ!?
nginxの設定
nginxの設定が必要最低限だったので修正。
ついでに静的ファイルはnginxから返すようにやっておいた。
worker_processes 8; events { worker_connections 10000; } http { sendfile on; include mime.types; upstream app { server 127.0.0.1:8080; keepalive 1000000; } server { location /images { alias /home/isucon/webapp/public/images; } location /stylesheets { alias /home/isucon/webapp/public/stylesheets; } location = /favicon.ico { log_not_found off; alias /home/isucon/webapp/public/favicon.ico; } location / { proxy_pass http://app; } } }
mysqlの設定
mysqlも必要最低限だったので設定
[mysqld] datadir=/var/lib/mysql socket=/var/lib/mysql/mysql.sock symbolic-links=0 max_allowed_packet=300M skip-name-resolve innodb_flush_log_at_trx_commit = 0 innodb_additional_mem_pool_size=40M innodb_log_buffer_size=32M innodb_log_file_size=256M innodb_buffer_pool_size=3600M max_connections = 2048 max_connect_errors = 10000 tmp_table_size=134217728 max_heap_table_size = 134217728 key_buffer_size = 512M table_cache=2048 sort_buffer_size = 1M read_buffer_size = 1M read_rnd_buffer_size = 4M myisam_sort_buffer_size = 1M thread_cache_size = 128 thread_concurrency = 8 [mysqld_safe] log-error=/var/log/mysqld.log pid-file=/var/run/mysqld/mysqld.pid
ネットワークやサーバー制限まわりの設定
このあたりでつまずくのは時間が勿体ないので、あらかじめ上限をあげておきました。
(ですので、ローカルポート枯渇などではひっかかってないです)
iptablesをOFF
ipconntracとか出て時間をくいたくないので念のため。
/etc/init.d/iptables stop chkconfig iptables off chkconfig iptables --list iptables -F
sysctl.conf の設定
この設定はvarnishの公式に「varnish使うならこんな設定まじおすすめ」みたいに紹介されてたもの。
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
ファイルディスクリプタでも時間をくいたくないのでlimits.confを設定。
* hard nofile 65535 * soft nofile 65535
ここまでやってみてスコアは2500とあまりかわらず。
MySQLがボトルネックなのはわかっていたのでボトルネックがうつった時に効果を発揮してくれるでしょう
インデックスを張る
onihsiがインデックスを張ってくれました
ALTER TABLE `isu4_qualifier`.`login_log` ADD INDEX `ip` (`ip` ASC), ADD INDEX `login` (`login` ASC), ADD INDEX `user_id-succeeded` (`user_id` ASC, `succeeded` ASC), ADD INDEX `ip-succeeded` (`ip` ASC, `succeeded` ASC), ADD INDEX `succeede-user_id` (`succeeded` ASC, `user_id` ASC);
これと上記のインフラ周りの修正でスコアは20000くらいになった。
banのユーザをredisでカウントするようにした
ipアドレス と ユーザー名を各々キーにしてログイン失敗で
$self->redis->incr($ip); $self->redis->incr($user->{id});
みたくインクリメント
ログイン成功で
$self->redis->set($user->{id}, 0); $self->redis->set($ip, 0);
ゼロに戻す
これでip_bunnedの確認とかが
sub ip_banned { my ($self, $ip) = @_; return undef unless $ip; my $count = $self->redis->get($ip) || 0; return $self->config->{ip_ban_threshold} <= $count; };
となる
でも/reportsの修正は怖かったのでlogin_logへのINSERTはそのまま。
こんな修正をmemememomoがばばーとやってくれた。
でも24000。あんまりのびなくてあるぇえええ?ってなる。
セッションの管理をメモリで行うようにした
@memememomoがベンチ回すと「max Open files」的なエラーが出るというので見てみると
/tmp内にあるセッションファイルがものごっつい量になっていた。
これ以上ファイルが書き込めないという状態になっていて1ディレクトリの上限ファイル数でも叩いてたのかな。(あんまり調べてない)
こんだけファイルができるくらいだからセッション書き込みにおけるディスクIOも問題となっているに違いない事に気づいて
memememomoにセッションをメモリで管理するように変更してもらった。
Plack::Session::Store::Cache Cache::FastMmap CHI
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
京都スイーツはギリギリだめでした。。。と思って読み進めると、失格してました。
・「京都スイーツ」チームは、 /mypage にログインユーザ名が表示されていないため、表示崩れとみなし、失格といたしました。
調べてみると SELECT * を書き換える時に必要なカラムが漏れてた模様。
どちらにしてもスコア足りてなかったので結果は同じですが失格は悲しい。
一応チェックされるほどの上位にいけたということで許して下さい、、、と会社に言い訳。
ここまでチェックするって運営さんはすごく大変だったろうな、、、と改めて感謝の気持ちでいっぱいです。
今年も楽しい問題、ありがとうございました。