2018/09/13

ISUCON7の予選問題を解いて、スコアを27万点まで上げた

こんにちは。フリーランスのエンジニアのあっとんです。
仕事が一段落したのもあってプチ夏休みという名の仕事が無い期間、ISUCON7 の予選問題をやったりしました。
それを改めてログにまとめる次第です。

結論から言えば、予選突破のボーダーの21万点を越えられました。
ちなみに累計作業時間は58時間くらいで、最終スコアは 270957 でした。
あとリモートワークのお仕事を募集中(2018/09/13現在)です。


どうして ISUCON の問題を触ってみようと思ったのか

  • Twitter でたまに見た単語で気になっていたので
  • ミドルウェアをインストールする事はあってもガリガリにチューニングする経験が無かったので
  • とりあえず数字で善し悪しが分かるので


自分レギュレーションと目標とか

  • スコア的な目標は予選突破ボーダー(21万点くらい)
  • とりあえず最初(ISUCON7予選)はブログとか見て良いのでノウハウを積む
  • ノウハウを積んだ上で本戦問題を時間制限+解説見ずに解く


環境構築とベンチマーカを動かす

まずは環境構築から。
とりあえず GCP の上に 4core 8GB の instance を上げる。
ISUCON7 の予選問題の README.md に従って環境構築。
初期スコアは5000点くらいだったと思います。
ベンチマークをかける側も 4core 8GB で環境構築。


なんとなくの知識で高速化をする

とりあえず知ってる範囲で適当に進めていく。
  • nginx の proxy を経由せずに直接 puma で 80番を bind するように
    • スコアが 3000 くらいになる。 却下。
  • rackup する時に environment を production にする
  • logger を無しにして log を disk に書かないようにする
    • 一応スコアが 5980 くらいにはなる
  • css と js を minify する
    • yui-compressor なるものを使う
    • $ yui-compressor main.css -o minimain.css # とか
    • Validation に失敗するのでこの手は使えない
  • gzip で圧縮してみる
    • .gz を置いて配る
    • そんなにスコアが変わんなかった
  • N+1 を解決したいが SET IN ... に書き換えるのが面倒
    • 普段 ActiveRecord を使っていたので includes で解決して欲しい……
    • (後から結局 sinatra-activerecord を入れることになる)
  • SELECT * の結果に対して first していた部分に LIMIT 1 を付ける
    • 1万点くらいになる
  • メモリの上に DB の全データを載せる
    • tmpfs + rsync で載せてしまう
    • 19340 点になる。倍。
  • mysql の log の WARN を取り除く
    • 22511 点になる。10%増し。


予測するな計測せよ

結局何が一番のネックなのかが分からないとどうしようもない。
ということで計測していく。
profiling には rack-mini-profiler を使ってみた。
さてこれでどこがネックなのか探るか、と思ったら作成されたファイルが読めない。
結論から言えばファイルに書く前に Marshal.dump されているせいで Marshal.load しないと読めなかった。
Marshal.dump を JSON.dump に monkey patch して無理に plain text にして jq で読む。
しかしファイルが多すぎて(200個とか)まともに読めない。
profiler を使うのは諦めて top だけで行くというゴリ押しをする。
(rack-mini-profiler を rack application で動かす、というのも記事にできそうなので後から書くかもしれない)
なお、初期の段階でベンチマークをかけると CPU を mysql が 55% 、puma が 35% くらい食っていて DB ネックだった。


参考文献を参照開始


1Core 1GB にスペックダウン



11万あたりの伸び悩み

その後もチマチマと変更を加えるものの、12万点の壁は越えられず。
CPU使用率も合計100%にならなくなった。
気付くのに結構かかってしまったけれど、オチとしてはメモリ不足。
具体的には残り10k とかになっていた。
というわけでここからは複数台構成にする。


複数台構成



lsyncd を使って画像の同期

解決すべき問題は icons を3台の間でどう整合性を取って返すか。
業務だったら S3 に置くなり CDN を使うのが良さそう。
しかし ISUCON なので、手元にあるサーバでどうにかする必要がある。
画像ファイル自体は1MB以下なのが保証されている。
なので書き込み時に memcached じゃなくて全サーバに書き込むことに。
そうすれば /icons を nginx が Cache-Control 付きで返してくれて良さそう。
(nfs で mount しても良かったのですが、結局 network access が発生するので早くならなそうだったので断念。)
(nfs の cache の config でどうにかなった可能性もゼロではないが……)
最初は inotify + rsync でゴリ押ししようと思ったのですが、 lsyncd なるものを発見
(rdiff-backup とか osync とか csync2 とか unison とか mirror とかあるがどれも古い)
サーバA は更新があったらサーバB に rsync。 B は C へ。 C は A へ。という形に。
これでファイルが upload されたら全サーバが共通のファイルを返せる。
結果、1台だけの時にスコアが 69770 に。3倍にできれば目標達成かな。


最終的な構成

3台 nginx にするより、2台 nginx + DB のがスコアが高かった。
あと mysqltuner というスクリプトを見つけて、それで値を調整。
最終的には score 270957 を達成したので脳内予選突破。



まとめ

  • ISUCON7 の予選問題のスコアを 27 万点くらいまで上げました。
  • 作業ノートによると58時間くらいかかっているらしい。
  • これで夏休みの自由研究2018としましょうか。



参考