ISUCON9 本選12位だった

ISUCON9 予選敗退した って書いたんだけど、アレがアレして色々あって、やっぱり本選に出場できることになったので参加してきた。

「失敗から学ぶISUCONの正しい歩き方」チーム、最終結果は12位でした。

言語は予選のときと同じRubyで挑戦。

10:09 初期状態 → 0

初期実装の状態では遅すぎてスコアが出ない、というところからスタート。当然ながらRuby実装に切り替えてもスコアは出ない。

まずはせめてスコアが出るくらいにはしてスタート地点に立たないとね、ってことで諸々サーバ側の準備を進めたりマニュアル読み込んだりしながら 最低限あるべきindex貼って対応していこうね、と作業開始

11:34 ようやくスコア出る → 1,429

さらにindexを貼っていくと、今度はFailするようになる。 実際にブラウザから確認してみると、どうもindexを貼ることによって検索結果の順番が変わってしまうらしい。 primary keyもORDER BY指定もなくて順番が保証されてないやん… 有り得ない、なんだこのクソアプリは!と憤りつつも ORDER BY でそれっぽい順番で返すようにしようとして train_name を指定し、今度はこれが数値に見せかけた文字列だったために 1 の次に 10, 100 が来るというバグを踏み抜き タイムロス。

13:46 ようやくバグが直った → 4,478

一通りindex貼り終わったものが動いて どうにか安心。 そういえば AVAILABLE_DAYS ってのがあるんだよね、ってことで ためしに 1030 に上げてみたところスコア変わらず。 うーん、じゃあ 90 だとどう? → 5,900 まで上がるが500errorなどが出てキツそう。とりあえず 90 で進めることにする。

14:47 複数台構成 → 6,517

id:Soudai さんにお任せしてMySQLをdockerから剥がしてもらって appサーバとdbサーバで分ける構成が動いた。負荷が分散されてスコアが上がる。たまたまこの瞬間で暫定1位の記録になったので慌ててスクショを撮った。

f:id:sugyan:20191007234341p:plain

15:15 get_available_seats 軽減 → 6,672

唯一と言える、僕がまともにコード書いた部分。 /api/train/search 内のN+1はとても複雑で、完全に排除するのは難しそうだったが 最も処理が重くて負担になっているのは get_available_seats を 「premium か否か」「is_smokingか否か」で計4回呼んでいる部分。この中で多数のJOINなどもあるし せめて1回の呼び出しで結果を取得して コード内でカウントすれば多少は早くなるだろう、と id:kamipo さんの提案を受けて実装。

https://github.com/soudai/isucon9-final/pull/5/files

特にバグも無く動いたは動いたが、残念ながらスコアにはあまり寄与しなかった。

そことは別に、なんか benchmarkerの /api/train/search リクエストで 500 errorを多く返していて、ログを見ると departurearrival が検索できず nil になるのにその値を参照しようとしてぬるぽで落ちているものが多数あって、かと言ってそれを応答から消したりエラー握り潰して誤魔化したりしてもどうやってもbenchmarkerに怒られて、どうしようもなくてハマり続けた。これは出題側のバグなんじゃないかなぁとボヤきながら色々試したが結局最後まで解決できず。悔いが残る。

15:39 deadlockの発見

このへんでbenchmarkerがFailしまくるようになり、どうも POST /api/user/reservations/*/cancel で500 errorを頻発しているようだが これも複雑な処理なので 500を返している箇所も複数あり 詳しい原因がよく分からない。

予選のときに得た教訓で、「泥臭いprintデバッグでも活用する」という精神で 画期的なcommitを入れた。

Add 500 error · soudai/isucon9-final@a8afb24 · GitHub

これにより journalctlcancel error!!!!!!!!! 6 を発見し deadlockによるものだというのがすぐに判明し 解決をkamipoさんに丸投げすることが出来た。 これが今回の最大の貢献だったかもしれない……

16:01 deadlock解決(?) → 12,056

これでようやくそこそこのスコアになったが

そこから先 細かいチューニングをするも 結局これより高いスコアが出ることもなく 最後の数十分はひたすら AVAILABLE_DAYS の数値を上げたり下げたりしてFailしないギリギリくらいに…と調整するくらいになってしまった。

しかし 90180くらいで試していて 初期の 10 付近の数値では試そうともしていなかった… もしかしたら敢えてこの数字を下げることでもっと良いスコアが出たかもしれなかったのかなぁ

18:00 競技終了 最終スコア 7,462

10位以内くらいには入りたかったが そこまでもいけなかったなぁ…

反省点

予選のとき以上にコードでの貢献が出来なかった。 1つの処理で何百行もあって複雑なloopのnestがあるような、言ってしまえばクソコード的なものを前に しっかり内容を読み取ってキレイにリファクタリングしていくだけの力が無かった。

どちらかというと方針の相談や 他のメンバーが詰まりそうになったときに横から一緒にみて解決する、みたいな役回りになってしまっていた。それはそれで貢献だったとは思うけど、それだけではどうしても手が足りないし 自分の役目をもっと果たすように動くべきだった、のかもしれない。

POST /api/user/reservations/*/cancel のところは最後まであまり解決できなかったのだけど、外部リクエストの部分を非同期化して まず200を返してしまってから後でゆっくり処理する、などの解法を聞いて、その発想は全然なかったなぁと思った。

感想

同じRubyで挑戦した白金動物園チームが見事に優勝して感動があったし、1人参加の学生さんが2位で驚愕だったし、多くの刺激を受けて とても楽しめたISUCONでした。

今年も本当にありがとうございました!

参照

soudai.hatenablog.com