ISUCON10予選にチームモツ鍋として参加した
2020年9月12日に開催された ISUCON10 予選に、チーム「モツ鍋」として @mintsu123 との2名で参加した。 最終スコアは 1691 で、予選通過はできなかった。
この記事では、予選でやったことについて自分の視点から淡々と記載していきたい。
なお、mintsu 氏のブログは以下にあるのでこちらも参考にどうぞ。
https://blog.mintsu-dev.com/posts/2020-09-15-isuccon10-qualify/
事前準備
今回、自分が ISUCON 前日までハッカソンのイベントに参加しており、そちらに時間を使っていたため ISUCON の準備はほぼ何もできなかった…。
一方、チームメンバーの mintsu 氏は過去の ISUCON 参加時に作ったスクリプトの整備や、予選の過去問を解くなどの準備をしてくれていたので、そのあたりの共有会を予選前日の夜に行った。
当日朝
当日、勢い余って8:00くらいには着席していたが、運営側のトラブルがあったようで12:20開始となった。
しかし何も準備していなかった自分としては、過去の戦略や具体的な作業方法についておさらいする時間を確保でき、恵みの雨となった。 この時間で、過去の予選で効果的だった戦略や新しく使えるかもしれない戦略について Google Meet でつないで調査・準備していた。
予選開始
予選開始の時間になり、サーバやリポジトリの初期セットアップ系作業(これを 初動 と呼んでいる)を mintsu 氏がやってくれていたので、自分はその間に 予選マニュアル をしっかり読んでおき、あとで要点だけさくっと伝えられるように準備をしていた。
また並行して、アプリケーションのおおまかな構成・仕様理解や DB スキーマの理解を進めていた。
最初の時点のベンチマークでは、スコアは 497 だった(言語は Go)。
Bot からのリクエストの遮断
予選のレギュレーションの中に、「Bot からのアクセスは503で弾いても問題ない」という主旨の記載があり、これは明らかに早い段階でやってしまったほうがいい案件だと判断し、すぐに対応を行った。
ざっと修正・反映をしてベンチマークを動かしてみたが、この段階ではログから Bot アクセスらしきものが確認できなかった。まだ「初期状態だし Bot からのリクエストはないのかな〜」と理解して、スコアは落ちていなかったからと master に修正を取り込んだ。
server {
if ($http_user_agent ~ ISUCONbot\(\-Mobile\)?) {
return 503;
}
// ... こんなのをたくさん記載
}
後に、この修正がうまく反映できておらず余計な時間を使うことになるとは、この段階では知らなかった…。 nginx の設定を雰囲気でいじるべきではなかったと反省している 🙇
MySQL サーバを分離
ベンチマークを回している中で、mysql プロセスの CPU 使用率が100%で張り付いているということに mintsu 氏が気づき、負荷分散のために MySQL サーバを別インスタンスで動かすようにした。
これで、1台目では Nginx + app、2台目で MySQL が動く状態になった。
index の追加
DB のスキーマを見ると、どのカラムにも index が貼られていないことがわかった。 一方で、index を活用できると高速化しそうなクエリが色々と使われていることがわかったので、ババっと index を追加した。
index 追加のような、アプリケーションに破壊的な変更を与えないものはざっとやってみて、「漏れがあればあとで追加すればいいし、問題が出れば戻せばいい」というスタンスでやっていた。
-- chairテーブル
CREATE INDEX idx_c_stock ON isuumo.chair(stock);
CREATE INDEX idx_c_price ON isuumo.chair(price);
CREATE INDEX idx_c_popularity ON isuumo.chair(popularity);
-- estateテーブル
CREATE INDEX idx_e_popularity ON isuumo.estate(popularity);
CREATE INDEX idx_e_latitude ON isuumo.estate(latitude);
CREATE INDEX idx_e_longitude ON isuumo.estate(longitude);
反映後にベンチマークを動かしてみたところ、スコアはそこまで上がらなかったが下がってもいなかったので master に反映。なおこの修正も後にきちんと反映されていないことに気が付くという凡ミスをしていた・・・(気づいてよかったね)。
ワイルドにやりすぎて動作確認もワイルドになっていたようだ。
POST /api/estate/nazotte の効率化
なぞって検索 では返却する物件数の最大数が50となっていたが、実装上は SQL で取得した物件を一通り処理したあとに50件以上であれば50で切り落とす…というようになっていて無駄があった。
なので、50件に到達した時点でレスポンスを返すように修正をした。
レプリケーションの設定
MySQL サーバの分離はしたが、まだ mysql プロセスの負荷が高いようだったので、負荷分散のために MySQL サーバを3台構成で動くようにした。 具体的には1台目を primary とし、残り2台を secondary としてレプリケーションを設定した。この辺は mintsu 氏のブログに詳しく書かれているのでご参照。
個人的に、この対応がスコアアップに一番有効だったんじゃないかと思っている(根拠はない)。
アプリケーションサーバの複数構成化
同様に、アプリケーションサーバも3台に分散するようにした。 しかし、更新系のクエリが発生するエンドポイントは primary DB があるインスタンスに寄せねばならないので、ここだけ気をつけて設定を行った。
// 以下3つのエンドポイントは更新が入る
location ~ ^/api/chair/buy/(.*)$ {
proxy_pass http://localhost:1323;
}
location = /api/chair {
proxy_pass http://localhost:1323;
}
location = /api/estate {
proxy_pass http://localhost:1323;
}
// ほかは更新が無いようだったので、全台へ分散させる
location /api {
proxy_set_header Host $http_host;
proxy_pass http://all;
}
バルクインサート
このあたりで、スコアが1000を超え始めるパフォーマンスを出し始めていたのだが、物件と椅子の CSV インポート処理がタイムアウトしてベンチマークに失敗するようになり、スコア0がしばらく続いていた。

CSV インポート処理は1件1件 INSERT 処理が行われていたので、ここをバルクインサートに修正する以外に残された道はないと判断。
このとき、残り1時間30分くらいだった気がする。これができないままだと0、できたときには予選突破もあるいは?という絶望と希望の狭間にいた。 前回の予選ではたしかスコア0かスコアが初期値から変動しないという屈辱を味わったので、二の舞になりたくない一心でペアプロに取り掛かった。
かなりトライアンドエラーを繰り返しながらの修正となったが、残り時間15分くらいのときに物件と椅子両方のバルクインポートが実装できて、スコアが1500近くになった。


残り数分
この時点で残り10分くらいしかなく、うかつなことはできない状態になったので nginx のログを止めたり、初動 のときに設定していた Go のプロファイラを無効化するなど非破壊的な変更をいくつか加え、最終スコアである1691に到達。
他にやりたかったこととして、ベンチマークエラーの原因切り分けのためにいくつかのエンドポイントに Sleep を挟んでおり、これを外したかったのだが間に合わなかった。うまく外せていればもう100 ~200くらいはスコア上がったんじゃないかと後悔しているが、予選突破のボーダーラインが2200くらいだったのでどのみち無理だったかな〜と今では思っている。

全体を通しての感想
今回も残念ながら予選突破とはならなかったが、過去の ISUCON 予選の失敗を結構活かすことができたと感じており、その点では良かったなぁと思う。
今回自分は準備も特にしていなく凡ミスも多く大変申し訳ない感じとなったが、有能な mintsu 氏のおかげで善戦できた。
かなりエキサイティングな1日を過ごすことができた。ISUCON 運営の皆様には感謝です。もし次回もあれば、是非参加させていただきたいと思う。