ISUCON9 予選敗退した って書いたんだけど、アレがアレして色々あって、やっぱり本選に出場できることになったので参加してきた。
「失敗から学ぶISUCONの正しい歩き方」チーム、最終結果は12位でした。
12位でした pic.twitter.com/HZZU6GbJgW
— すぎゃーん💯 (@sugyan) October 5, 2019
言語は予選のときと同じ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
ってのがあるんだよね、ってことで ためしに 10
→ 30
に上げてみたところスコア変わらず。
うーん、じゃあ 90
だとどう? → 5,900
まで上がるが500errorなどが出てキツそう。とりあえず 90
で進めることにする。
14:47 複数台構成 → 6,517
id:Soudai さんにお任せしてMySQLをdockerから剥がしてもらって appサーバとdbサーバで分ける構成が動いた。負荷が分散されてスコアが上がる。たまたまこの瞬間で暫定1位の記録になったので慌ててスクショを撮った。
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を多く返していて、ログを見ると departure
や arrival
が検索できず 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
これにより journalctl
で cancel error!!!!!!!!! 6
を発見し deadlockによるものだというのがすぐに判明し 解決をkamipoさんに丸投げすることが出来た。
これが今回の最大の貢献だったかもしれない……
16:01 deadlock解決(?) → 12,056
これでようやくそこそこのスコアになったが
そこから先 細かいチューニングをするも 結局これより高いスコアが出ることもなく 最後の数十分はひたすら AVAILABLE_DAYS
の数値を上げたり下げたりしてFailしないギリギリくらいに…と調整するくらいになってしまった。
しかし 90
〜180
くらいで試していて 初期の 10
付近の数値では試そうともしていなかった… もしかしたら敢えてこの数字を下げることでもっと良いスコアが出たかもしれなかったのかなぁ
18:00 競技終了 最終スコア 7,462
10位以内くらいには入りたかったが そこまでもいけなかったなぁ…
反省点
予選のとき以上にコードでの貢献が出来なかった。 1つの処理で何百行もあって複雑なloopのnestがあるような、言ってしまえばクソコード的なものを前に しっかり内容を読み取ってキレイにリファクタリングしていくだけの力が無かった。
どちらかというと方針の相談や 他のメンバーが詰まりそうになったときに横から一緒にみて解決する、みたいな役回りになってしまっていた。それはそれで貢献だったとは思うけど、それだけではどうしても手が足りないし 自分の役目をもっと果たすように動くべきだった、のかもしれない。
POST /api/user/reservations/*/cancel
のところは最後まであまり解決できなかったのだけど、外部リクエストの部分を非同期化して まず200
を返してしまってから後でゆっくり処理する、などの解法を聞いて、その発想は全然なかったなぁと思った。
感想
同じRubyで挑戦した白金動物園チームが見事に優勝して感動があったし、1人参加の学生さんが2位で驚愕だったし、多くの刺激を受けて とても楽しめたISUCONでした。
今年も本当にありがとうございました!