ISUCON 5予選を突破してきました #isucon

「ピザはバランスいい」チームでISUCON2日目に参戦し、2日目7位くらい、全体を通して15位となり本戦に進めることになりました。めでたい!! メンバーは@takashabe@zakiry、nohamaの3名。なお最終スコアは14923でした。

isucon.net

以下、準備したことや当日行ったことを書いておきます。

チームメンバーのブログは以下。@zakiryがアプリ側から見た目線で書いています。

ISUCON5に参加しました - zakiry’s diary

準備したこと

ISUCON部の設立

去年ISUCONを知ってからずっと出てみたかったので、まず社内でメンバーを集めるところからスタートした。そこで部活という体裁で毎週月木定時後に2−3時間練習を重ねることにした。 集まったメンバーは僕含めWebアプリケーションエンジニアで、過去問を題材にしてミドルウェアの設定やチューニングの勘所みたいなところを勉強していった。 初回にとりあえずやってみようということでISUCON 4予選をそれぞれ2時間チューニングしてみるということをやり、各々初期スコアから全く動かなかったのは良い思い出である。 最終的に予選は9名3チームで出場している。

秘伝のタレの準備

必ず使うであろうNginxやMySQLの設定ファイルやログ解析ツール導入をコピペで済ませられるよう、秘伝のタレを準備し、ISUCON部のgithub issuesで共有した。

役割分担、方向性の決定

チームが決定した後は、各チームでメンバーの役割分担とチューニング方向性の決定を行った。 弊チームは僕以外がRubyに詳しいこともあり、僕がインフラ、他2人がアプリという分担とした。言語、ミドルウェアについてはRuby + Nginx + MySQLという基本的な構成で行くことにした。 例年予選はクエリ改善やアプリチューニングを確実に行っていけば勝てるという見込みもあった。

時間配分の決定

以下のようにざっくりとした時間配分を決めておいた。ほぼほぼこの通りに動けたと思う。

  • 10:00〜11:00
    • 問題、アプリケーションを読む
  • 11:00〜
    • 順当にチューニングする
  • 15:00
    • 実装が困難そうな大規模な改修を行うデッドライン
  • 17:30〜18:00(終了)
    • 再起動テスト

当日やったこと(インフラ目線)

競技開始前後

  • 作業環境である弊社オフィスに集まる。口頭でコミュニケーション出来るレベルの距離にいることを意識した。
  • インスタンスを立てて、ssh鍵、zsh、解析ツール類の準備。この辺りは事前にコピペすれば整うように手順を用意していた。
  • インスタンスを立てている間に並行して当日マニュアルにチーム全員で目を通した。得点や失格条件、終了時にあるべき状態などを重点的に読み合わせた。

11時〜14時辺り

  • MySQLでslowlogが出せなくてハマる。mysql> set global slow_query_time=0; を行い、全ログが出力されるはずが、コンソールから叩いたものだけログに吐かれて、アプリケーションにアクセスした時のログが一切出ないというよく分からん状況が続いた。結局原因不明だったが、他メンバーがunicornの設定をいじった時に表示されるようになり、まぁいいかという感じだった。
  • pt-query-digestでクエリ解析出来るようになったので、ベンチを回してひたすら解析結果を共有していた
  • アプリの再起動やNginx(kataribe)、MySQL(pt-query-digest)のログ解析用に簡単なスクリプトを作る
  • 他の2人が順調にボトルネックとなっているクエリを改善し、13時半くらいでスコア10,000出て暫定1位となる。弁当食べる手が震えていた。

14時〜15時辺り

  • 順調にスコアが伸びてきたので一旦再起動テストを実施するとスコアが4,000くらいになり焦った。今回はベンチ実行時にDB初期化が無いため、innodb_buffer_poolにデータが乗ってないことを疑った。そこでウォームアップのためにinnodb_buffer_pool_dump_at_shutdown、innodb_buffer_pool_load_at_startupをmy.cnfに投入してみたが設定が反映されず焦った(後述)。
  • my.cnfの設定が反映されていない件、実は /etc/my.cnf ではなく、 /etc/mysql/my.cnf を読んでいることが分かった。そこから更に !include していたので /etc/mysql/mysql.conf.d/mysqld.cnf に諸々の設定を投入していった。
  • この段階でinnodb_buffer_pool_sizeを2Gにし、MySQL再起動もコンスタントにメモリ3G弱を使いきれるようになっていた。マシン再起動後のスコアもキープ出来ていることを確認した。
  • tcp_max_tw_buckets などのカーネルパラメータの秘伝のタレを投入
  • この時点でスコア10,000〜12,000くらい。

15時〜17時辺り

  • 引き続き他メンバーがボトルネックとなるクエリを片付けていき、僕がひたすらベンチを回しては解析するという作業を実施していた。
  • commentsentries テーブルのデータ構造を変更すれば早くなることが分かり、alter文を流してみるも全く処理が進まない事象に遭遇した。データ量が膨大で、かつディスクがHDDのために長時間かかってしまったのが原因であった。30分経っても終わらないのでalterは諦め、別のクエリを改善する方針を取った。
  • ベンチを回しながらnginxのworker数を調整し、最終的にworker_processes=4、worker_connections=10240辺りに落ち着いた。
  • 静的ファイルの数が多くはなかったが、やらないよりはマシだろうということでnginxから静的ファイルを返すようにした。
  • この時点でスコア15,000〜16,000くらい。

競技終了前後

  • MySQLのslowlogやNginxのアクセスログを外した。
  • ベンチを回しながらunicornのworker数を調整していた。10/16/32くらいで試行し、どれもそれほど大差無かったので32のままにした。今思えば、スコアが伸びないのであればもう少し下げても良かったかもしれない。
  • 再起動テストを行い、特に問題ないことを確認した。
  • 17時30分過ぎにボトルネックとなりそうなクエリを発見し、改善を試みたがFAILが取れないため17時55分時点でRevertした
  • 終了間際はとりあえずベンチキュー入れておくみたいな状態になっており、Revert時点の14923が最終スコアとなった。結果的にLatest Scoreが最終的なスコアとなったため、ここでもしFailしていたら危なかったかもしれない。

まとめ

全員初出場だったが、しっかりプロファイリングしてボトルネックを潰していたら予選通過することが出来た。 そういう意味では定期的に勉強会を行い、それなりに準備は出来たのかなと思う。

他チームの解法を見ていてより感じましたが、様々な方向性で高スコアを出すことが出来るとても素晴らしい問題であったと思います。そしていくつかトラブルも発生していたようですが、丁寧に対応されており、運営の皆様には感謝の言葉しかありません。 非常に楽しい機会を頂きありがとうございました。本戦もどうぞよろしくお願いします。

そしてひたすらボトルネックを潰してくれた優秀なチームメンバーのおかげで予選を通過出来ました。ありがとうございました。本戦も頑張りましょう!!!!