Tsuzu's Notes

主にIT系備忘録

ISUCON10予選で再起動試験失敗で敗退しました

TL:DR

  • 教訓: レギュレーションはちゃんと読み込もう(N回目)

はじめに

3000点以上を取っていたのに再起動試験でコケて予選敗退した悔しさの勢いでブログを書いています。ultra_fast_gopherという学生3人チームで参加していました。

やったこと

一週間前

とりあえず去年惨敗したからちゃんと練習しようとISUCON9予選を解き直した。環境をAWSのEC2上に構築したが4台ぐらい用意したら結構な金額が取られてしまった、かなしい。

New Relicも試したがTransportを入れ替えたりcontextを取り回したりしてもなぜか外部API呼び出しが可視化されなかったのとpprofで別段困らなかったのでpprofとmysqldumpslowの二刀流のみで戦うことにした。

高速化もいろいろ実践したが重要なのは事前準備だなぁと思いながら、phpMyAdmin、netdata、WireGuard(VPN、今回は外から直接叩けないため)、GitHub Actionのself-hosted runner(CD用)などを諸々セットアップするためのスクリプトを用意した。

当日

〜開始

今回は集まらず全員がDiscordでリモートで参加したが実際に集まるよりモニターが多いという点でめちゃくちゃ快適だった。来年もリモートで良さそう。

開始時間が遅くなったのでのんびりおにぎりとペットボトルとラムネをコンビニに買い出しに行った。ちょうど雨に降られて悲しかった。

開始〜初動

Bad Gateway!?インスタンス一覧見られない!?となっていたがチームメイトがGistの方からインスタンスのアドレスを見つけてきてくれたので事前準備したスクリプトを一人一台流し込んでセットアップした。(GitHub Actions のrunnnerはnode1のみ)

(そういえばVPNが51820ポートでListenしていたがつながらなかったのでNTPのポートなら行けるだろ、と思って試したらいけてしまったが良かったのだろうか・・・。)

とりあえずpprofとslow query logの設定のみ行いベンチマーク実行。インスタンスが同じなのを忘れて最初pprofを見てJSONエンコードが遅い??となっていてDebugの無効化などをしたがそもそもMySQLの方が圧倒的にCPUを食っていたのでnode2にMySQLを移行し、再度実行。この時node1からのログインをphpMyAdminでサクッと可能にできて楽だった。

slow query logを見てぽちぽちインデックスを追加する(~16:00ぐらい?)

この時点で1000ぐらいだったと思う。大体ここら辺でチームメイトがUser-Agentの対応を実装してくれていた。展開できるものはそもそも正規表現を使わずstrings.Contains等にしておいた。多分負荷がそもそも高くなかったのでこの時はそこまで影響がなかった。

中盤

椅子のサイズから納入できる物件をレコメンドする関数が時間を圧倒的に使っているがインデックスの追加でどう早くするかわからなくなる。

全てのpopularityを負の値にするのとDESC>ASCのORDER BYでMySQL 8ならインデックスがDESC, ASCで貼れるのを思いつく。前者はバグらせた時がやばそうだったのでとりあえずnode2をMySQL8にあげてquery cacheの設定を削除。再びベンチマークをかけるがかなりスコアが下がってしまった。やっぱダメじゃんと言ってnode3にDBを移行。

ここでDB分割すればよくね?と思ってnode1とnode3の分割を実行。大体300ぐらい上がった気がする。(1500ぐらい?)

やっぱりMySQLが依然重いのでインメモリ戦略に切り替える。大体のデータはpopularityとidでソートしたうえで最初の数百件ぐらい持ってくれば良いだろうという考えからbtreeライブラリを持ってきて物件の全データをbtree(今回は2分木)で展開し、10000件以上アクセスしたらやばそうなのでDBにフォールバックするようにした。

感想戦を見ているとそもそもpopularityとidに適切にインデックスを貼るだけで良かったらしく無駄だったっぽいので真似はしないでください。

大体これで1800~ぐらいには行っていた気がする。

中盤〜終盤

ここらへんでslow queryが検索でばらけていてよくわからなくなってくる。検索がやばそうだけどとりあえずpprofに立ち戻るとNazotteがかなり時間を消費していることがわかる。 N+1が起こっているのとこれは別にDBでやる必要全くないな、ということでGoでのチェックに切り替える。

凸多角形の内外判定をしてくれる関数をインターネットで漁りまくりGoに書き換える作業を行う。

実装が下手。修正してとりあえず投げるがverifyで無限に落ちる。始点と終点が同一なのがやばそうということで調整するがそれでもダメ。DBでの結果と比較して間違っているものを落としてきた。いい感じのツールがわからないのでチームメイトに投げると可視化してくれた。↓これでバグるのは流石に実装が悪そうということになるがバグが見つからない。

f:id:tsuyochi23182:20200913013221p:plain

(言いがかりの可能性も高いが)参考実装が間違ってるのでは?と別の実装を持ってきて始点終点を調整するとようやく通る。(私の実装が間違っていた説も高いです。)

これで大体3000弱ぐらいのスコアが出るようになったので不要なものを片っ端から無効化して上振れを狙って何度かベンチマークを回し3200以上が出たので再起動試験等をしつつ残り20分は椅子を温めました。

結果

そもそも再起動後にベンチマーカーが動作するだけでなくデータも保持されている必要があることを知らなかった。ISUCON初心者ですか???

それでもDBへのフォールバック機能があるためデータが読み込みのみであれば問題なかったが書き込まれるとインメモリを使ってしまうため20件以上書き込まれるとフォールバックされず死んでしまう。

座標の方についてはデータはDBから参照しているため問題なく動いていたはずである。というわけでおそらくだが↓のPRをmergeすれば予選突破できていたと思う。

github.com

もはや何の意味もないスコアだが、かなり健闘をできていた方だったはずなので本当に悔しい。

総評

毎度毎度ドキュメントちゃんと読もうな?っていう反省をしている気がする。8の本戦では負荷をあげる機能に気がつかなかった。

今回はちゃんと準備していてパフォーマンスも完璧だっただけに本当に悔しい。GitHub ActionsとWireGuardは最高だった。事前準備はとても重要です。そしてドキュメントはディスプレイに穴が開くほど読みましょう・・・。

また挑戦するために来年開催されることを願っています。そして来年こそは必ず本戦に出場して入賞を目指します。

謝辞

運営の方々、楽しいコンテストをありがとうございました。本当にお疲れ様でした。