ばとらの部屋
ISUCON14に参加しました(batora視点)
作成日 2024年12月8日 / 最終更新日 2024年12月9日
今回、初めてISUCONに参加しました。チーム名は「Maxipus」で、サークルメンバーのyukikamome316と、nakamuraitsukiと共に参加しました。
プログラミングサークル「Maximum」からは、他にも「Maxif.」「Maximum-Y.N.K.S」が参加しました。
過去のISUCONの問題を題材にして、Maximumサークル内で部内練習会を数回行いました。サークル内で得点の計測用レンタルサーバーを用意し、ベンチマーク測定&部内のLeaderboardを用いて本番形式で行いました。
こんな感じ↓
私は、Goを使ったバックエンドの開発は少ししか経験がなく、インフラ周りの知識もない状態からのスタートでした。部内練習会を通して、pprofを使ったり、スロークエリログを取って問題のあるクエリを見つけ、インデックスを張ったり、N+1問題を解消したりバルクインサートを使ったりと、いろいろな対策を学びました。
当日は私の家に集合し、3人でパソコンを広げて準備をしました。私とnakamuraitsukiが初参加だったこともあり、本番で何かあったときにすぐに話し合って対応できるように対面で作業することにしました。
ライブ配信を見て、出題内容をチェック!
ISUCON14 出題動画
ISUCON14 : https://isucon.net/archives/58818382.htmlナレーション:平山笑美 / https://www.pirami.online/制作:株式会社プロモータル / http://promotal.jp/
https://youtu.be/UFlcAUvWvrY?si=dySfPFYmOh55Vaam
ISURIDE...面白そう!ワクワクといった感じでした(笑)
まずは、競技開始と共に全員がssh接続できることを確認!(←まずここからです)
私はまず簡単そうなデバックモード無効化に取り掛かろうとしたものの...無えなぁ
nakamuraitsukiがpprofを導入し、ベンチマークを取って遅そうなところを探しました。
足早にyukikamome316がスロークエリにインデックスを張り、私はコードを眺めながら怪しそうなところを探しました。
得点:910→2922
yukikamome316がgetChairStatus
のN+1を解消に取り掛りました。
その間にスロークエリログからownerGetChairs
関数内のクソデカクエリが遅いことが分かり取り掛かりました。改善策が分からずGPTに投げたが、それでも点数は伸びず断念。
次に、遅そうなchairGetNotification
関数を見るとインデックスが張られているにもかかわらず遅いことがわかりました。nakamuraitsukiとクエリを一つ一つ眺めましたが、なぜ遅いのかが分かりませんでした。そこで、SELECT * FROM users WHERE id = ? FOR SHARE
から共有ロックがかけられていることでトランザクションの待ち時間による遅延が発生しているのではないかと考え一旦消してみましたが、結局点数は伸びず断念。
得点:2922→3050
yukikamome316がサーバー分割を行おうと試みるもうまくいかず停滞。
やはりchairGetNotification
が遅く、ride_statuses
を取得するのに冗長なクエリがあるように見え、テーブルの結合などを試みましたが、結局点数は伸びず。
// rides と ride_statuses を結合して最新の状態を取得
query := `
SELECT rs.id, rs.ride_id, rs.status
FROM rides AS r
INNER JOIN ride_statuses AS rs ON r.id = rs.ride_id
WHERE r.chair_id = ? AND rs.chair_sent_at IS NULL
ORDER BY rs.created_at ASC
LIMIT 1;
`
しかし、yukikamome316がchairs
テーブルの複合インデックスの順序を変更、rides
にカバリングインデックスを貼ったことで点数が伸びました。
得点:3050→3244
nakamuraitsukiとコードをひたすら眺めていると、各クエリ自体は遅くないが別の関数から多く呼ばれていることで全体として遅くなっているのでは?と考え、キャッシュ化をしようと思いました。
手始めにchairAuthMiddleware
でaccess_token
, user
をmap
で保持するようにキャッシュ化してみました。
var appUserCache = cache.NewWriteHeavyCache[string, *User]() // キャッシュをグローバルで共有
// ミドルウェア関数の修正
func appAuthMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
c, err := r.Cookie("app_session")
if errors.Is(err, http.ErrNoCookie) || c.Value == "" {
writeError(w, http.StatusUnauthorized, errors.New("app_session cookie is required"))
return
}
// Cookieの値を取得
accessToken := c.Value
// キャッシュからユーザー情報を取得
user, found := appUserCache.Get(accessToken)
if !found {
// キャッシュにデータがなければデータベースから取得
user = &User{}
err = db.GetContext(ctx, user, "SELECT * FROM users WHERE access_token = ?", accessToken)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
writeError(w, http.StatusUnauthorized, errors.New("invalid access token"))
return
}
writeError(w, http.StatusInternalServerError, err)
return
}
// キャッシュに保存(TTLは設定しない)
appUserCache.Set(accessToken, user)
}
// コンテキストにユーザー情報を設定
ctx = context.WithValue(ctx, "user", user)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
この実装をchairAuthMiddleware
にも適応して測定したところ、得点が伸びました!
得点:3244→4027
あれこれ細かいことを試してみようと思うが、残り時間が少なく実装→ベンチ測定までできない可能性があるためログ消しに移行。
ログを消したが何故か点数が伸びなかった...しまいには3000点台に戻ってしまった。
結局最後のベンチマーク測定では得点が3546点で終了しました。
今回のISUCON14のリポジトリはこちらに公開しています。
20時でのライブ配信による結果発表で、同サークルの「Maxif.」が総合29位で入賞!となりましたが、Maxipusは21時ごろに公開された順位では総合423位、学生順位50位という結果でした。
この順位は、レギュレーション違反による順位の変動前の結果なので目安といった感じです。
追記(12/9):https://isucon.net/archives/58837992.html で正式に結果が発表されました
最終的にはレギュ落ちしていました...なんでだろう(Maximum-Y.N.K.Sもレギュ落ちしていましたね...)
最後のenvcheckもやったし、ちゃんと計測もできてたのになぁ...再起動試験で落ちるってなんなんですかねー...
少しモヤっとした気持ちで終わってしまいました。
競技終了後、一部のMaxif.メンバー、Maximum-Y.N.K.Sメンバーと合流し、打ち上げ(?)をしました。
我々のチームでの反省点としては、
良かった点としては、
こんな感じかな~来年に向けてバックエンドでのインターンにも興味が沸いてきたので挑戦してみたいなと思いました。まあ今回の問題に関しては、競プロ的な思考が求められる部分もあったため、幅広く知識を増やしておかなければなと感じました。