仕事が一段落したのもあってプチ夏休みという名の仕事が無い期間、ISUCON7 の予選問題をやったりしました。
それを改めてログにまとめる次第です。
結論から言えば、予選突破のボーダーの21万点を越えられました。
ちなみに累計作業時間は58時間くらいで、最終スコアは 270957 でした。
あとリモートワークのお仕事を募集中(2018/09/13現在)です。
あとリモートワークのお仕事を募集中(2018/09/13現在)です。
どうして ISUCON の問題を触ってみようと思ったのか
- Twitter でたまに見た単語で気になっていたので
- ミドルウェアをインストールする事はあってもガリガリにチューニングする経験が無かったので
- とりあえず数字で善し悪しが分かるので
自分レギュレーションと目標とか
- スコア的な目標は予選突破ボーダー(21万点くらい)
- とりあえず最初(ISUCON7予選)はブログとか見て良いのでノウハウを積む
- ノウハウを積んだ上で本戦問題を時間制限+解説見ずに解く
環境構築とベンチマーカを動かす
まずは環境構築から。
とりあえず GCP の上に 4core 8GB の instance を上げる。
ISUCON7 の予選問題の README.md に従って環境構築。
初期スコアは5000点くらいだったと思います。
ベンチマークをかける側も 4core 8GB で環境構築。
ベンチマークをかける側も 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 ネックだった。
参考文献を参照開始
- 予選2日目3位のチームの方の記事などを参考にし始める。
- sinatra-activerecord を導入
- migration を導入
- /icons/:file_name を sinatra で handle せずに nginx で handle するように
- ベンチマーク前に DB から画像を icons ディレクトリに書き込んでおく
- image の upload 時に icons の下に書くようにした
- これでベンチマークをかけられるようになった
- nginx に cache の設定を入れる
- スコアは 24582 で思っていたほど上がらない
1Core 1GB にスペックダウン
- ここで ISUCON7 の予選は (1core 1GB)*3 であることが判明
- GCP の instance をスペックダウンさせる
- スコアが 5233 になる。
- N+1 を削っていく。
- /messages の中 (score: 6297)
- /fetch の中 (score: 74003)!!
- N+1 を削りきった途端スコアが10倍になる。
- (この辺から middleware tuning したかった趣旨がどっか行ったと思い始める)
- Statement.close 忘れを直す
- これだけで (score: 89003) になる。
- ちょっとしたことでスコアが上がるので嬉しい頃。
- /history の N+1 を潰す (score: 83439)
- ちょっと下がる。
- というかたぶん誤差とかの範囲な気がする。
- きちんとするなら何回か測って平均を載せるのが正しそう。
- MySQL の binary log を吐くタイミングを transaction ごとでなく秒数ごとにする
- (score: 101696) になった。ついに10万の大台に。
- /profile の変更時の update を1回にまとめる
- (score: 114849) になる。
- Statement.close 忘れを直す
- スコアが若干下がる。
- この辺りから伸び悩み始める。
11万あたりの伸び悩み
その後もチマチマと変更を加えるものの、12万点の壁は越えられず。
CPU使用率も合計100%にならなくなった。
気付くのに結構かかってしまったけれど、オチとしてはメモリ不足。
具体的には残り10k とかになっていた。
というわけでここからは複数台構成にする。
複数台構成
- まずは一番メモリを食っていたDBを別のサーバに移す。
- 次に複数台構成だと icon がどこに upload されるか分からない
- ので memcached で集中的に管理することに
- MySQL があるサーバに memcached を置く
- /profile への post 時に memcached に set するようにする
- 最終的には dalli という gem を使って実装
- サーバ一台だと (score: 45464)
- かなり落ちた
- internal bandwidth 次第だけれど network access が発生しているので仕方がない
- nginx server を 2 台にする
- (score: 96002)
- 二倍になった。大変理想的。
- nginx を DB server にも立てる
- (score: 139975) 3倍くらい。
- memcached の key に '/icons/' を prefix で置いてたのを修正
- あと namespace が必要なさそうだったので削る
- (score: 184209)
- 結構上がった。あと3万点。
- この辺で迷走することになる
lsyncd を使って画像の同期
解決すべき問題は icons を3台の間でどう整合性を取って返すか。
業務だったら S3 に置くなり CDN を使うのが良さそう。
しかし ISUCON なので、手元にあるサーバでどうにかする必要がある。
画像ファイル自体は1MB以下なのが保証されている。
なので書き込み時に memcached じゃなくて全サーバに書き込むことに。
そうすれば /icons を nginx が Cache-Control 付きで返してくれて良さそう。
(nfs で mount しても良かったのですが、結局 network access が発生するので早くならなそうだったので断念。)
(nfs の cache の config でどうにかなった可能性もゼロではないが……)
(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としましょうか。
参考
- ISUCON7 予選問題の参照実装とベンチマーカー. Contribute to atton/isucon7-qualify development by creating an account on GitHub.
- ISUCON7 まとめ : ISUCON公式Blog
- ISUCON7 本選出場者決定のお知らせ : ISUCON公式Blog
- GitHub - isucon/isucon7-qualify: ISUCON7 予選問題の参照実装とベンチマーカー
- Ubuntu Manpage: yui-compressor - JavaScript/CSS minifier
- GitHub - janko-m/sinatra-activerecord: Extends Sinatra with ActiveRecord helper methods and Rake tasks.
- GitHub - MiniProfiler/rack-mini-profiler: Profiler for your development and production Ruby rack apps.
- ISUCON 7 予選2日目を3位で通過しました - 酒日記 はてな支店
- ISUCON 夏期講習 2017 を開催しました(当日の資料あり) : ISUCON公式Blog
- ISUCON7予選を2日目3位で突破した - beatsync.net
- コマンド一つでMysqlを速くする - Qiita
- GitHub - mperham/memcache-client: Ruby library for accessing memcached.
- GitHub - petergoldstein/dalli: High performance memcached client for Ruby
- Module ngx_http_memcached_module
- GitHub - axkibe/lsyncd: Lsyncd (Live Syncing Daemon) synchronizes local directories with remote targets
- Lsyncd - The Configuration File
- GitHub - sol1/rdiff-backup: rdiff-backup
- How to Optimize MySQL Performance Using MySQLTuner
0 件のコメント:
コメントを投稿