こんにちは、バックエンドエンジニアのmakinoです。ISUCON11本選から1ヶ月半が経過し、2年連続失格の傷が癒えてきたので振り返りブログを書いていこうと思います。
ISUCON11本選について
本選の題材は大学の履修登録サイトでした。 すでに作問陣による本選問題の解説と講評が出ているので、問題の詳細については下記の記事をご参照ください。 isucon.net
今回の問題は、高負荷な箇所を改善してもスコアが一気に上がるようなことはなく、じわじわ伸びていくような問題だったので競技時間中はずっと苦しんでいました。 また、本選出場30チーム中12チームが失格になったことからも、問題の難易度が窺えるかと思います。
弊チーム「カレーおじさん」の最終スコアは68,347点でしたが、最後の負荷走行にてfailになってしまい失格となってしまいました。 また、運営の方が競技終了後に一時的にサーバーを開放してくれていましたが、そのアナウンスに気付かなったのでエラーログを回収できず、また本選と同様の環境で再現を試みましたが再現できず、ということで失格の原因は闇に消えてしまいました。
失格という結果自体は残念ですが、上位25チームに入れば良い予選と違い、本選では少しでも上の順位に行くために時間ギリギリまで攻めていたので、ある程度致し方ないかと思っています。 今回はどうすれば失格を回避できたかというよりも、より高得点を出すためにはどう立ち回れば良かったかという観点で振り返っていきたいと思います。
今回の記事では改善の詳細には触れないですが、本選のレポジトリは公開していますので気になった方はご覧ください。 github.com
シナリオ分析が甘かった
近年のISUCON本選ではただ漫然とボトルネックを解消するだけではなく、ベンチマーカーのシナリオを読み解き、よりアプリケーションの仕様に沿った最適化をすることが重要になってきている傾向があります。 今年のISUCON作問者へのインタビューでも、シナリオを読み解く重要性に触れられていました。 codezine.jp
今回はベンチマーカーによるユーザーのアクセスパターンを読み解くために、nginxのアクセスログにuser_idを吐くようにしました。 具体的には、Goの参照実装ではechoというフレームワークが使われており、ログイン確認用のmiddlewareがすでにあったので、そこで下記のようにレスポンスヘッダーにuser_idを加える実装をしました。
// IsLoggedIn ログイン確認用middleware func (h *handlers) IsLoggedIn(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { // 中略 // userIDを付与 c.Response().Header().Set("X-Isu-UserId", userID.(string)) return next(c) } }
仕込みが終わったところで、一人の学生ユーザーに焦点を当てて、アクセスログからユーザーの行動を特定しようとしたのですが、 「お知らせを閲覧する」→「課題の提出をする」 が繰り返されているだけで、特に示唆を得ることができず、アクセスログからのシナリオ分析は早々に諦めてしまいました。
後日の運営陣による解説を聞いていたところ、今回のシナリオでキーになっていたのは教員の方で、学生と教員の行動が直列になっていることにより、教員が利用するエンドポイントの改善を行うことで学生の課題の提出頻度が増え、スコアが上がるようなシナリオになっているとのことでした。 アプリケーションマニュアルをよく読むと直列に行動が行われることが示唆される記述もありました。
教員や同じ授業を取っている学生にも着目していれば、もう少しシナリオの理解が深まったのではないかと思います。
チーム間のコミュニケーションが不足していた
中盤以降になると /api/users/me/grades
がタイムアウトしたことによって学生が10秒間待機している、というのが頻発するようになりました。
ここのメソッドを改善しないとスコアが伸びないだろうということで、2人がかりで改善に取り組むことにしました。/api/users/me/grades
のエンドポイントはコード量が多かったため、「学生の成績取得」と「GPAの統計値の計算」で分担していたのですが、「学生の成績取得」の改善は終わったものの「GPAの統計値の計算」の改善は時間内に間に合いませんでした。
ただ、競技終了後に確認したところ、「学生の成績取得」の改善のために用意した中間テーブルを「GPAの統計値の計算」でも使用していれば改善の工数は大幅に短縮できたということが分かりました。改修内容をチームメンバー間で共有していれば、、と悔やみました。 今回はオンラインで競技に取り組んでいたこともあり、いつもよりコミュニケーションが不足してしまっていたと思います。実装方針が決まった段階で手を動かす前に他のメンバーに相談してみる、行き詰まったらスクリーンを共有してペアプロしてみる、などのアクションを取れると良かったかなと思います。
思考の柔軟性が足りなかった
上記の「GPAの統計値の計算」の改善ですが、弊チームは中間テーブルなどを駆使して改善しようとしていましたが、上位陣はまるっとキャッシュすることで改善していたようです。 GPAの統計値は刻一刻と変わる情報なので、キャッシュできるはずがないと思いこんでしまっていましたが、とりあえず試して見るという精神も必要だったなと反省しています。
ちなみに、ISUCON9の予選のときにも同じようなミスをしていました。このときは外部アプリケーションが頻繁に502を返す状況に陥り、仕様に記載のない502に対してどうハンドリングしていいかが分からず結局そのまま競技終了してしまいましたが、予選通過していたチームは単にリトライすることで乗り切っていたということが分かりました。
ISUCONでは普段の業務とは少し頭の使い方を変えて、レギュレーションで明示的に禁止されていなければやってみる姿勢が必要だなと思いました。
まとめ
解き応えがあり、本選にふさわしい難易度の問題だったと思います。来年は今年の反省を生かして、今度こそ本選の上位進出を果たしたいと思います。
運営の皆様、ありがとうございました。また来年の開催を楽しみにしています。 (来年の本選はコロナが落ち着いてオフライン開催になるといいなあと思っています)
We are Hiring!!
弊社ミラティブは今年のISUCON11のスポンサーになったり、ISUCON部が発足したりと、社内でもISUCON熱が高まっています。 バックエンドエンジニアは日々大量のトラフィックと向き合っており、ISUCONやパフォーマンス改善が好きな方はとても楽しめる環境だと思いますので、応募をお待ちしています!