ISUCON5 予選通過しました (@methane編)
9/27 の ISUCON 予選2日目に参戦してきました。
KLab から参加した6チームのうち予選通過できたのは私が率いる lily white だけ、それも通過組の中で下から3位とかなり厳しい結果になってしまいました。
本格的な練習は新人が予選で ISUCON の難しさを実感してからにしようと思っていたのですが、今年は予選のレベルが想像以上に上がっていて、 お題のアプリも本戦さながらの規模、複雑さになっていて、もう完全に舐めてましたごめんなさい。出題側本気出しすぎです。本当にお疲れ様でした。
考察と感想戦はベンチマーカーが公開されてからにするとして、当日の流れを覚えているうちに振り返ってみます。 (時間とスコアをメモってなくて集計サイトもクローズしてしまったので、文中の時間とスコアはうろ覚えのものです)
準備
lily white は私以外に新卒の @gam0022, そして Twitter で見かけて誘った学生の @k2wanko さんの3人チームですが、 2人がサーバーサイドの経験が殆ど無い上に一緒に練習する時間も取れなかったので、 一応なにが必要なのかは教えたうえで個別練習してもらい、本戦も個別で挑戦することに。
私は Ubuntu 14.04 で OpenResty をビルドしておいたり、「マスタリング nginx」を読んだり、去年用意したチートシートを アップデートしたりしていました。
roマウント問題
スタート時点では余裕ぶっこいてたので、若者2人のサポートをしつつ一人で余裕で予選クリアするつもりでした。
2人にはマニュアルを読んでもらいつつ、 sysctl, nginx, my.cnf, limits.conf などを一通り設定して、
Go 実装にはバグがあるという情報があったので Ruby 実装のまま一回ベンチマークをかけつつ、
show full processlist
で目につくクエリをメモったりしてた。
ここまで30分程度で、それから当日マニュアルをゆっくり読もうとしたとき、スナップショットから起動した インスタンスがおかしいと言われ、調べたら / が ro マウントされてた。
ブートプロセスに詳しくないのでちょっと時間を食ったけど、 fstab で LABEL=cloudimg-rootfs
となってるデバイスを
見つけられないようだったので /dev/sda1
と書き換えてひとまず解決。
(帰ってからあとで調べたところ、何故かラベルが設定されていなかったので sudo e2label /dev/sda1 cloudimg-rootfs
が
正しい解決方法だったようです。)
スタートダッシュが遅れたけど、終了直前にこの問題にぶつかったらヤバかったので結果オーライ。 スナップショットを撮り直して、マニュアルを読みなおし、やっとアプリに取り掛かれるようになりました。 (11:00ごろ)
エントリが重い
ro マウント問題を調査している間に2人には元のイメージから起動してアプリを動かしてみてもらっていて、どうやら Go 実装で普通に ベンチマークを通るらしいので、 Go に切り替えて、どんなアプリかみたり、 top や dstat を眺めたりした。最初に考えたのは、
- エントリ、コメント、足あと、チューニング対象になりそうな機能が3つも! 出題者頑張りすぎ!
- IO Wait がネック。どうやら read らしい
- にしてはアプリの CPU も遅い。なにか無駄なことしてそう。
真っ先に確認しないといけないのはもちろん IO wait です。メモリに乗るか乗らないかは致命的な問題だからです。
/var/lib/mysql/isucon5q# ls -lah total 2.4G drwx------ 2 mysql mysql 4.0K Sep 26 06:17 . drwx------ 5 mysql mysql 4.0K Sep 27 11:15 .. -rw-rw---- 1 mysql mysql 8.6K Sep 26 06:13 comments.frm -rw-rw---- 1 mysql mysql 628M Sep 27 11:24 comments.ibd -rw-rw---- 1 mysql mysql 67 Sep 25 23:16 db.opt -rw-rw---- 1 mysql mysql 8.5K Sep 25 23:28 entries.frm -rw-rw---- 1 mysql mysql 2.3G Sep 27 11:24 entries.ibd -rw-rw---- 1 mysql mysql 8.5K Sep 25 23:19 footprints.frm -rw-rw---- 1 mysql mysql 40M Sep 27 11:24 footprints.ibd -rw-rw---- 1 mysql mysql 8.6K Sep 25 23:19 profiles.frm -rw-rw---- 1 mysql mysql 464K Sep 27 11:23 profiles.ibd -rw-rw---- 1 mysql mysql 8.5K Sep 25 23:19 relations.frm -rw-rw---- 1 mysql mysql 60M Sep 27 11:24 relations.ibd -rw-rw---- 1 mysql mysql 8.4K Sep 25 23:19 salts.frm -rw-rw---- 1 mysql mysql 256K Sep 25 23:19 salts.ibd -rw-rw---- 1 mysql mysql 8.6K Sep 25 23:19 users.frm -rw-rw---- 1 mysql mysql 9.0M Sep 25 23:19 users.ibd
指定されたインスタンスタイプの n1-highcpu-4 の搭載メモリは 3.6GB なので、2.4GBならなんとかメモリに載る。 今回の出題はアプリだけでなくデータセットも絶妙なバランスですね。
アプリのメモリは500MBもあれば十分だし、OSのバッファ/キャッシュも今回は問題にならないので、 innodb のバッファプールを ちょっと多めの 2.7GB に設定してから、次のクエリでバッファプールを温めはじめました。
mysql> select sum(length(body)) from entries;
このクエリがなかなか返ってこないので、アプリを読み始めます。 (11:40 ごろ)
テンプレート毎回コンパイルしてる問題
テンプレートをリクエストの度にファイルからコンパイルしてるのが目に止まりました。 去年の予選でも Go のアプリを重くしてる一番の原因だったので即対応開始。
普通にテンプレートを起動時にコンパイルするように書き換えただけだとコンパイルエラー。 テンプレート内から呼び出せる関数をテンプレートコンパイル時に指定するのですが、その関数にクロージャーを渡していて、 そのクロージャーが Request と ResponseWriter に依存しているために、リクエストハンドラ以外に移動すると コンパイルできなくないことが判明。
関数経由で取得していた情報をテンプレートのレンダリング時に明示的に渡すように書き換えてテンプレートをコンパイルするようにし、 ついでに友達のコメントをトップページに表示する際にコメント先のエントリが private かどうかチェックするためだけに大きい Entry の bodyを 読んでいるところを削る。
これだけでまずは1000点を超えていたはず。
entries テーブルの圧縮
タイトルを取得するためだけに大きい entry テーブルの body を select しているところがボトルネックになっているので、 title と body を分離するついでに、ウォームアップ時間を短縮するために圧縮テーブルを使うように。
この変換スクリプトは Python で書いて、 id の範囲でざっくり2分割して並列実行。それでも結構時間を食った。
myprofiler を見ながらクエリチューニング
15時を回っていたけど上位勢から大きく離されていてかなり焦りだす。 myprofiler を見て重いクエリを各個撃破していくことに。
まずは友達のチェックに時間がかかっていたので、 friends
テーブルをオンメモリ化。
エントリのユーザーを指定してコメントを引っ張ってくるところのJOINを高速化するために、 comments
テーブルに
entry_user_id
カラムを追加。ここまでで 12000 点台。
それでもトップページでコメント1000件取得が重いので、クエリ結果をキャッシュするように改造した所、 バグって手間取る。 Go のプログラムが panic&recover をLLの例外の用に気軽に使ってる上に、そのエラーメッセージをHTMLに書くだけなので、 PHP で display_errors だけ使ってデバッグするくらいのデバッグ難易度で時間を無駄にしてしまう。
いったんリセットするも、このままでは予選突破できなさそうなので、最後の30分で最新のコメント1000件をオンメモリ化し、 14000点台に。 最後に再起動を確認したらダメだったので慌てて対策して終了。
感想
予選なら一人で余裕で突破できるだろうと思ってたけど、全く余裕が無かった。
複数台構成の組み合わせが必須になる以前の、ISUCON, ISUCON2 の流れでさらに難しくしたような問題で、 これが本戦でも全くおかしくないし、これが本戦だったらやはり10位以下で惨敗だったでしょう。
トップの fujiwara 組と比べると、8時間の競技時間中6時間半の時点のスコアは超えているので、一人にしては悪くない パフォーマンスだと思うけど、同レベルで得意分野が異なる3人チームでないと fujiwara 組に勝つのは難しい。 3人で力を合わせないと勝てないという点でも、これぞISUCONと言える予選でした。
残り2人が高負荷サイトのアプリチューニングの面でも、インフラの面でも経験値がほぼ0なので、 本戦に向けてどうチームビルディングしていくか悩ましいところです。
来年は戦力の分散を止めて、勉強組、遊び組、攻略組でチームを作ろうと思います。
最後に
インフラを提供して頂いた Google さんと、サポートしてくださった佐藤さんありがとうございました。 GCE は起動が早いし、課金単位時間も短いので、個人環境としてすごい使いやすかったです。 頂いたクーポンがいっぱい余ってるので本戦の練習でもガンガン活用します。
271組ものチームのサポート業を1人で対応されていた LINE の櫛井さんも本当にお疲れ様でした。 オンサイトの本戦はさらに大変そうですが、よろしくお願いします。
そして出題側の Treasure Data の田籠さん、上薗さん、初期実装協力者の方々、本当にお疲れ様でした。 Twitter で田籠さんの忙しい様子を見て、さすがに予選はある程度手を抜いてくるだろうとたかをくくっていたのですが、 予想を遥かに超えた、本戦レベルの出題や採点システムに完敗しました。 本戦はもっとまともに戦えるように準備してリベンジしに行きます。