#isucon 4にfujiwara組として参加しました
ISUCON4の予選にfujiwara組として参加したのでその感想です。
- fujiwaraさん(ISUCON1,2優勝/ISUCON3出題)
- acidlemonさん(ISUCON3出題)
- handlename(ISUCON3予選敗退)
というメンバーでした。(アレッ一人浮いてる!)
まとめ
長いのでまとめを最初に書いておきます
- 最終スコアは67782
- アプリが激薄だったので、できるだけ外部(MySQLとか)への接続がなくなるよう変更を入れた
- 静的ファイルの点数を捨てた
- 無理せず普段から使い慣れているもの(Perl,Redis,MySQL)を使った
- 少なくとも競技中に見えていたものについて、やるべきことは全てできた
- ISUCONたのしい
以下時系列順でお送りします。 主に自分がやった変更を書いています。 誰がやったか覚えているものについてはカッコ書きで名前を書いてます。
fujiwaraさんとacidlemonさんの記事もあわせて読んでもらうのがといいかと。
前々日
- 作戦会議をして代替の分担を決める
- fujiwara: インフラまわり設定調査計測
- acidlemon: アプリまわり設定調査計測
- handlename: 調査計測を元にひたすらコード書く
- Githubにprivate repoを用意(acidlemon)
- いろいろ予想して策を立てても逆に嵌りそうだから 具体的な打ち手は問題を見てからですね、ってことに
前日
- 予選1日目
- Twitterや中間/最終結果を見ながらそわそわする
6:00
- 寝坊がこわいのでいつもよりだいぶ早く起きる
9:00
- 遅刻がこわいのでだいぶ早く会場(会社)に着く
会社のいちばん景色がいい会議室でスタンばってる #isucon pic.twitter.com/iwoED9Mo9p
— NAGATA Hiroaki (@handlename) 2014, 9月 28
10:00
- サーバー上のセットアップfujiwaraさんとacidlemonさんにお願いしてブラウザからアプリを確認
「この銀行、ログインしかできない・・・?」
- もしかしてひたすらコード書く担当、仕事がないのでは・・・
10:15
- Githubにpushされたコードを読む
- 使用言語はいつも使い慣れているperl
- やっぱりログインしかしていない
- ログイン失敗したユーザーとIPアドレスをレポートするAPIがあるようだ
- テンプレートも変なことはしていないのでSQLを読む
10:26 < handlename> ip_bannedは前回ログイン成功時以降の失敗数をカウントして、閾値よりも上ならban扱いしてる 10:27 < handlename> password hashはどこかにキャッシュしておく?カラム追加でもいいか 10:28 < acidlemon> users自体が完全に書き換わらない静的データだから 10:28 < acidlemon> コードに直書きしないとトップになれないかもしれないね 10:29 < acidlemon> 幸いIDは完全な連番だから 10:29 < acidlemon> 配列にしてO(1)でアクセスできる
10:30
- とりあえず素の状態のスコアが出る
10:49 < fujiwara> perl 10:49:44 type:score success:7890 fail:0 score:1705
11:00
sub user_locked
とsub ip_banned
の中のクエリを書き換えてみたけど(handlename) インデックス張ったら(acidlemon)Uuse index
になったので結局使わなかった
mysql isu4_qualifier > explain SELECT COUNT(1) AS failures FROM login_log WHERE user_id = 1 AND id > IFNULL((select id from login_log where user_id = 1 AND succeeded = 1 ORDER BY id DESC LIMIT 1), 0); +----+-------------+-----------+------+-------------------------------------+-----------------------------+---------+-------------+------+--------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-----------+------+-------------------------------------+-----------------------------+---------+-------------+------+--------------------------+ | 1 | PRIMARY | login_log | ref | PRIMARY,login_log_user_id_succeeded | login_log_user_id_succeeded | 5 | const | 1 | Using where; Using index | | 2 | SUBQUERY | login_log | ref | login_log_user_id_succeeded | login_log_user_id_succeeded | 6 | const,const | 1 | Using where; Using index | +----+-------------+-----------+------+-------------------------------------+-----------------------------+---------+-------------+------+--------------------------+ 2 rows in set (0.03 sec)
- この時点でスコアは13000くらい
- アプリから返していた静的ファイルをnginxから返す設定が完了(fujiwara)
11:19 < fujiwara> 11:18:37 type:score success:78732 fail:358 score:17077
- これだけで素の状態の10倍
- 明らかになおした方がいいところがとりあえず潰れたので、 アクセスログからベンチマーカーの挙動を確認(handlename)
14865 GET /stylesheets/bootflat.min.css 14853 GET /stylesheets/bootstrap.min.css 14838 GET /stylesheets/isucon-bank.css 14837 GET /images/isucon-bank.png 13473 GET / 7438 POST /login 1403 GET /mypage
- 静的ファイルを除くと
/
>/login
>/mypage
の順でリクエストが多いようだ /report
はベンチ終わってからしか呼ばれないので放置
13:00
- セッションの保存先がファイル(
Plack::Session::Store::File
)だったのでファイルの保存先を/dev/shm
に変更(fujiwara) - パスワードのハッシュを毎回計算しているのをなくした(handlename)
- 生のパスワードは
dummy_user.tsv
に書かれているので、これを起動時にメモリに載せてしまう(handlename)- 良い子は生パスワードを保存するようなコードを書いてはいけないよ!
- 結果
/login
でmysqlからselectしなくなった
13:51 < handlename> 13:50:59 type:score success:88650 fail:0 score:19149 13:51 < handlename> ^workload=1
14:00
14:34 < handlename> MyIsamにした場合 14:33:29 type:score success:165690 fail:0 score:35792
- 呼び出し回数の多い
/login
について対策 - ログイン失敗理由を集計したら
wrong_password
が一番多かった
4145 wrong_password 1221 succeeded 865 banned 431 locked 3 wrong_login
wrong_password
最初に判断するようにした(handlename)- Varnishでインデックスをキャッシュした(fujiwara)
- ログイン時のエラーメッセージは場合によって変わるので、エラーではない(=リダイレクトではない)ものだけキャッシュする
14:53 < handlename> 14:53:35 type:score success:150220 fail:0 score:32449 14:53 < handlename> ^varnish in 15:07 < handlename> workload=6 15:06:25 type:score success:176020 fail:0 score:38023 15:09 < handlename> workload=7 15:09:00 type:score success:179270 fail:0 score:38727
- アプリその他がCPUを使いきってくれないことに悩む
15:00
- Starlet -> Starmanに変える(handlename)
15:31 < handlename> starletに変えた 15:31:35 type:score success:185810 fail:0 score:40138
- いちおう
Devel::NYTProf
でアプリのプロファイルも取ったがDB周りが重いだけだったので打ち手にはつながらず - カントリーマアム派とホームパイ派で抗争が勃発
ホームパイとカントリーマアムのどちらが良いかで仲間割れしてる #isucon
— Masatoshi Kawazoe (@acidlemon) 2014, 9月 28
— NAGATA Hiroaki (@handlename) 2014, 9月 28
うるせーカントリーマアム投げつけっぞ! #isucon
— Masatoshi Kawazoe (@acidlemon) 2014, 9月 28
仲良くしてください カントリーマアムください #isucon
— そらは (@sora_h) 2014, 9月 28
16:00
- 抗争は続いている模様
カントリーマアムで仲間割れしたのでお互い全然別のチューニングやってる #isucon
— Masatoshi Kawazoe (@acidlemon) 2014, 9月 28
- mysqlをredisに置き換える実装が完成(acidlemon)
- がっつり書き換えたので最初failするも修正して3000ほどスコアが上がる
17:10 < handlename> 17:09:50 type:score success:201030 fail:0 score:43425 17:12 < handlename> ^workload=7 17:12 < handlename> workload=8 17:12:07 type:score success:199460 fail:0 score:43087 17:14 < handlename> workers=30 17:14:27 type:score success:194210 fail:0 score:41953 17:14 < handlename> ^workload=6
16:50
- カントリーマアム派とホームパイ派が和解
— Masatoshi Kawazoe (@acidlemon) 2014, 9月 28
なんとなくノリで張り合ったけどカントリーマアムも好きです #isucon
— NAGATA Hiroaki (@handlename) 2014, 9月 28
17:00
- 中間発表
- この時点では4位
#isucon オンライン予選二日目 残り1時間となった17時 時点での順位は以下の通りです
1. rapid
2. 椅子子
3. .dat
4. fujiwara組
5. Ooops!
続く pic.twitter.com/TLqe2dKf7j
— くしい (@941) 2014, 9月 28
17:30
base.tx
を使わずはじめからテンプレートを結合しておく(handlename)- ベンチマーカーにはCSSを返さないようにする (handlename)
- failせず、ブラウザから見た時にはcssを返すのでレギュレーション違反では・・・ない
- 具体的にはJSからdocument.writeでlinkタグを出力するようにした
禁断の魔術に手を染めるときが来た(適当) #isucon
— Masatoshi Kawazoe (@acidlemon) 2014, 9月 28
- スッキリしない変更だがこれで大幅にスコアが上がる
cssのlink消すのは最後まで逡巡したんだけど、人が見たときのJavaScriptが動作するブラウザで見た目に変化なければokという運営の確認が取れたので…まあ失格ならそれで仕方ないですね #isucon
— fujiwara (@fujiwara) 2014, 9月 29
17:45 < handlename> workload=7 7:44:38 type:score success:133840 fail:0 score:68263
17:45
- /reportのチェックに失敗していることに気づく
「エッ、あと15分しか無いんだけど???」
17:55
- redisに保存していた値をすべてmysqlに移し、
/report
のレスポンス作成処理自体には手を加えない、という実装方法だった - mysqlに移すところでinsert順が逆になっていたのが原因(
/report
作成にlog_.id
の大小を利用しているため) l
をr
に変えたら通ったのでなんとかスコア送信
あぶねー #isucon
— fujiwara (@fujiwara) 2014, 9月 28
冷えた #isucon
— NAGATA Hiroaki (@handlename) 2014, 9月 28
肝を冷やした… #isucon
— Masatoshi Kawazoe (@acidlemon) 2014, 9月 28
18:00
最終スコアと終了後に試しにworkload上げてベンチ取った結果。
18:01 < handlename> 最終スコア workload=7 17:57:18 type:score success:132900 fail:0 score:67782 18:01 < handlename> 参考 workload=8 17:59:10 type:score success:137004 fail:0 score:69876 18:15 < handlename> 参考 workload=9 18:15:23 type:score success:139828 fail:0 score:71318 18:18 < handlename> 参考 workload=10 18:17:53 type:score success:139504 fail:0 score:71153
IRCのログを見てもわかるように、自分がベンチ回す担当だったので
/report
のチェックにfailしていることに気づいてなかったことに気づいた時は死ぬかと思った・・・。
スコアのグラフはなかなかドラマチックなものになったようです。
本日の結果です、最後の3分が熱すぎる / ISUCON4 オンライン予選 二日目の結果発表 : ISUCON公式Blog http://t.co/Tqlr3X4vZk #isucon pic.twitter.com/MH1NECm5bd
— くしい (@941) 2014, 9月 28
すごい。
戦い終わって大荒れの机の状況をご覧ください #isucon pic.twitter.com/yjjghKhJtS
— fujiwara (@fujiwara) 2014, 9月 28
20:00
- AMI提出諸々完了
祝勝会(気が早い)の写真を撮る様子です #isucon pic.twitter.com/Tr0qjb4Bkr
— NAGATA Hiroaki (@handlename) 2014, 9月 28
運営の皆さん、参加者の皆さんお疲れ様でした。 本戦で会いましょう!(レギュレーション違反で落とされてなければ!)