ISUCON 3 予選参戦記
10/5 土曜日はISUCON 3 の予選一日目に参加していました。
KLab からは 2 チームが、「ぜかまし」は Go, 「真面目系社内ニート 」は PHP での参戦でした。 私は「ぜかまし」で、結果は2位で本戦進出が決まりました。
その時のコードがこちらになります
振り返り
まずは、 tmux に残っていたベンチマーク履歴をご覧ください
2013/10/05 17:33:46 Score: 2485.3 2013/10/05 17:35:08 Score: 2021.4 2013/10/05 17:36:24 Score: 1786.6 2013/10/05 17:43:33 Score: 13635.2 2013/10/05 17:46:20 Score: 13882.8 [OK] 結果を管理サーバに送信しました 2013/10/05 17:48:55 Score: 15093.7 [OK] 結果を管理サーバに送信しました 2013/10/05 17:52:28 Score: 15065.0 [OK] 結果を管理サーバに送信しました 2013/10/05 17:56:59 Score: 31199.0 [OK] 結果を管理サーバに送信しました 2013/10/05 17:58:45 Score: 32486.8 [OK] 結果を管理サーバに送信しました 2013/10/05 18:00:52 Score: 31133.5 [ERROR] API呼び出しで予期しないエラーが発生しました. 運営に問い合わせてください.
「結果を管理サーバーに送信しました」と出ているのが、予選のスコアとして提出している場所です。予選は18時までだったのですが、17時40分までは全然スコアが出ていません。
それが、17:43 にいきなり1万点を突破し、そこからスコア提出と最後の追い上げをしているのがわかると思います。
課題となるプログラムは Markdown を使ったメモアプリで、ログイン機能があるので過去のISUCONに比べると単純なページキャッシュはしにくくなっていました。
ぜかましでは、1人が独立してインデックスを貼るなどの普通のチューニングをしてスコアを提出しつつ、僕ともう一人が Go のプログラムの大幅書き換えをしていました。
書き換えの方針は、書き込み処理はそのままにしつつ、データベースのスキーマやインデックスには一切手を付けず、アプリケーション内にすべてのデータを持つというものです。起動時にすべてのデータをメモリ上に読み込むので、ベンチマーク中は一切参照系クエリを出す必要がありません。 メモの投稿も、単純なINSERT文でインデックスが必要ないので、データベースのチューニングは不要です。
午後4時頃までには一部のページを覗いて書き換えが完了し、バグもなかったのですが、問題が発生しました。全然CPUを使い切れないのです。top 読みで、 USR 20%, SYS 30%, IDLE 50% くらいです。
そこから17:30までは、必死でCPUを使い切れない原因を探っていました。実は Markdown から HTML への変換は外部の Perl プログラムなのですが、本当はそこがボトルネックになる状態を意図していたのに、うまくいかないのです。外部プログラムとの通信が一時ファイルを使っていたのを pipe にしたり、 Goroutine を分けてみたり、他にどこかロックがあって性能が上がらない可能性を考えて2時間くらい格闘したものの、まったくスコアが上がりませんでした。
17:35ごろ、もう半ば諦めていたのですが、ダメ元で "Markdown golang" でググって最初にヒットした "github.com/knieriem/markdown" を使ってみました。チェックプログラムの仕様が不明のため、出力されるHTMLが変わるとテストが通らなくなることをおそれ、そこがボトルネックになるまでは手を付けずに居たのです。そして、結果、テストは1発で通り、1万点を超えました。この時はもうひたすら笑うしかありませんでした。
もう一度スコア登録用にベンチマークを走らせ (17:46)、さらに負荷試験プログラム側・サーバー側の並列度を調整してベンチマークを走らせ(17:48, 17:52)ながら、急いで手つかずだった「最近登録されたメモ」を見るページを、メモリ上の情報を読み込むように書き換えました。 (17:56) 最後にもう少しだけパフォーマンス上の問題を修正して (17:58) 終了しました。
反省
今回の反省点は、リクエスト処理中に外部プロセスを起動するという、どう考えても良くないアーキテクチャを、テスト失敗を恐れて後回しにしたことです。
よく、「早すぎるチューニングは悪」とか「まず計測して、本当に問題があるのか、ボトルネックはどこなのか把握する」と言われますが、一方で正しく計測し判断すること自体の難しさはあまり語られず、まるでツールさえ知っていればだれでも正しく計測してボトルネックを判断できるように誤解されがちです。(シンプルな正解を謳う言葉は、理想的で人の心を惹きつけますが、現実世界とは異なります)
アーキテクチャを複雑にする・コードを増やしたり読みにくくする最適化は悪ですが、アーキテクチャをシンプルにする最適化は、高速化しつつ正しい計測と判断をしやすくしてくれるので、もっと積極的になってもいいのです。
今回はこの問題で時間を使ってしまいましたが、もっと早く markdown 変換を置き換えていれば、 バックグラウンドで Markdown->HTML変換を投機実行しておく (外部プロセス呼び出しだと重すぎ・遅すぎて空き時間に十分な投機実行ができなかった) 、最近作られたメモ一覧のページでログインユーザーに依存しない部分だけページキャッシュする (Go のテンプレートエンジンは遅い) などの最適化ができたはずです。本戦に向けてもう少しやり残したことをやってみるつもりです。
最後になりましたが、この非常に準備が難しいイベントを主催してくださった KAYAC さん、本当にありがとうございました。本戦もよろしくお願いします。