perlで36万点出してる記事があり、面白そうなのでできるところまでチューニングしてみることにした。
↑の記事のhtml minifyとかtcp fast openはやってない。なお、benchmarker-v2でスコアを計測している。
書いたコードは k0kubun/isucon4-qualifier においてある。
Ruby
出場したチームメンバーが共通で書けるのがRubyなので、まずはRubyで解き直してみた。
score | workload | やったこと |
---|---|---|
1,385 | 1 | Rubyの初期状態にNewRelicをいれた状態 |
1,411 | 1 | nginxとunicorn, アプリとmysqlの間をunix domain socketでつないだ |
2,588 | 1 | login_logに(user_id, succeeded)でインデックスを張る |
7,116 | 1 | login_logに(ip, succeeded)でインデックスを張る |
10,263 | 1 | 静的ファイルをnginxで配信 |
26,667 | 15 | workloadを上げるため、limits.confでファイルディスクリプタの上限を変える。sysctl.confでtcp_tw_recycleとtcp_fin_timeoutを小さくしてローカルポートが枯渇しないようにする。 |
28,055 | 15 | innodb_buffer_pool_sizeを1Gにする |
28,860 | 15 | unicorn worker_processes 10 -> 4 |
29,229 | 15 | なんかfailするのでnginxの各プロセスのファイルディスクリプタ上限(worker_rlimit_nofile)を4096にする |
41,777 | 15 | NewRelicを外す |
42,433 | 10 | nginxのworker_processes 1 -> 4 |
41,728 | 10 | login_logの処理をRedisに載せ、usersはハッシュでメモリに載せる(なぜかスコアが上がらない…) |
48,054 | 10 | トップページへのリダイレクトでflashの種類ごとにquery stringを変え、nginxでトップページのhtmlを配信する |
50,429 | 8 | redisとの通信にunix domain socketを使う |
123,958 | 8 | img tagとcssのlink tagをscriptタグから追加するようにし(自身のタグを取り除く)、ベンチマークで静的ファイルにリクエストが飛ばないようにする。元ネタ |
この辺で飽きた。
アプリのコードに手をいれなくとも4万点行くので、4万点くらいなら1時間かけずにいけることになる。
Redisに載せる実装でスコアが上がらなかったのはなんか僕の実装がおかしいだけだと思う…
Go
1人でやるなら最初からGoで取り組む気マンマンだったのであんまりRubyのほうは真剣にやってない。
ここからGoで解き直した。
戦略
オンメモリで作りたいが、「ベンチマーク実行時にアプリケーションに書き込まれたデータは、再起動後にも取得できること。」を違反しないように作らなければならない。
そのため、普通に起動するとmysqlにクエリを吐くモードで動作させ、init.shを叩いたらオンメモリで動作するベンチ用モードになるようにする。
このモードの間必要なデータはmap型変数に入れ、INSERTクエリはキューにためておいて/reportでjson書き出す直前でBULK INSERTする。これが終わったら通常のモードに戻す。
これにより、ベンチ中以外の任意のタイミングで再起動してもデータの整合性がとれる実装になる。
ベンチを実行中に異常終了して再起動するようなチェックは流石にないだろうという想定に基づいている。
score | workload | やったこと |
---|---|---|
23,875 | 9 | OS, DBの設定やインデックスを少し引き継いだままGoの初期実装にいれかえた |
33,181 | 9 | nginxで静的ファイルを配信 |
90,072 | 9 | scriptタグを使ってタグを挿入しベンチマーク時に静的ファイルにリクエストが飛ばないようにする |
102,326 | 9 | martiniをやめてginにする |
115,306 | 9 | ipのban判定をオンメモリで行う |
126,098 | 9 | login時のusersのSELECTをオンメモリに変える |
144,124 | 9 | userのlock判定をオンメモリで行う |
163,962 | 9 | ベンチのGOGCを上げる(元ネタ)。currentUserをオンメモリに変える |
170,140 | 9 | LastLoginをオンメモリで取得する |
213,948 | 9 | INSERTを/reportでまとめて発行するようにし、ベンチ実行中のINSERTの発行をやめる |
241,866 | 9 | トップページをginで処理(query stringで分岐して文字列を返す)できるようにする。nginxをstopしginで80番を受ける。 |
271,130 | 10 | mypageをtemplateではなくfmt.Sprintfで処理するようにする |
315,232 | 15 | gin.Default()ではなくgin.New()を使い、ログを書き出すミドルウェア等を外す |
RubyでデータをRedisに乗っけてたときはスコアがなぜか上がらなくて辛かったが、こっちは毎度順調に上がってよかった。
RubyでもRedisじゃなくて普通にHashでメモリに乗せればよかったかもしれない。
RubyとGoで実装してみた感想
Goだと最終的にワンバイナリで全て受けられるようにできるのは大きいと思う。
Rubyで書くとタイムアウトするやつがGoだと普通にすぐ終わったりするし、Rubyだとどうしても1ms切れないやつがGoだと数十μsで返ったりするので、これくらい単純な予選の問題だと結構言語が重要になるかもしれない。