💺

ISUCON13に参加しました

2023/12/01に公開

これは、LAPRAS Advent Calendar 2023の1日目の記事です

https://qiita.com/advent-calendar/2023/lapras


この度、11/25(土)に開催されたISUCON13に参加しましたので、備忘録を残したいと思います💺

https://isucon.net/archives/57801192.html

例年は弊社内でいくつかチーム結成があったのですが今年はなく、私のみ個人的に参加する形となりました
風物詩として、同僚のdenzowがPython実装で本選出場を目指すというのがあり、今年は拝めなかったのが残念ではあります

https://www.denzow.me/entry/2021/09/12/013136

結果

Go実装にて最終スコア14995 、順位は149/661位でした

https://isucon.net/archives/57993937.html

以下は、メンバーのkangaechuがCopilotに頼んでプロットしてもらったスコアの時系列推移になります


点数の推移

source
import matplotlib.pyplot as plt
from datetime import datetime, timedelta
import pytz
​
# データをリストに格納します
data = [
    "0-20231125-040431",
    "0-20231125-040831",
    "0-20231125-041317",
    "0-20231125-042412",
    "0-20231125-062917",
    "0-20231125-072511",
    "0-20231125-074627",
    "0-20231125-080049",
    "0-20231125-080952",
    "0-20231125-082432",
    "10379-20231125-035438",
    "11272-20231125-053242",
    "11403-20231125-055355",
    "11691-20231125-055758",
    "12018-20231125-055018",
    "12635-20231125-081340",
    "12841-20231125-064406",
    "13092-20231125-073444",
    "13128-20231125-073029",
    "13196-20231125-071213",
    "13217-20231125-065711",
    "13217-20231125-070615",
    "13359-20231125-063529",
    "13400-20231125-060644",
    "13422-20231125-064652",
    "13560-20231125-062516",
    "14144-20231125-081819",
    "14348-20231125-075741",
    "14607-20231125-082145",
    "14717-20231125-082841",
    "14722-20231125-083055",
    "3626-20231125-023449",
    "3670-20231125-015534",
    "3672-20231125-024242",
    "6277-20231125-074005",
    "7021-20231125-032620",
    "8662-20231125-043011",
    "9215-20231125-042228",
    "9244-20231125-043854",
]# スコアと日時を抽出します
scores = [int(d.split('-')[0]) for d in data]
dates = [datetime.strptime(''.join(d.split('-')[1:]), "%Y%m%d%H%M%S") for d in data]# タイムゾーンを設定します
# jst = pytz.timezone('Asia/Tokyo')# 日時にタイムゾーンを適用します
# dates = [jst.localize(date) for date in dates]
​
dates = [date + timedelta(hours=9) for date in dates]
# データを日時でソートします
sorted_data = sorted(zip(dates, scores))# ソートされた日時とスコアを抽出します
sorted_dates = [d[0] for d in sorted_data]
sorted_scores = [d[1] for d in sorted_data]# 折れ線グラフを作成します
import matplotlib.dates as mdates
​
plt.plot(sorted_dates, sorted_scores)
plt.xlabel('Time')
plt.ylabel('Score')# 横軸のフォーマットを設定
plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%H:%M'))
​
plt.show()

以下、試合終了までにあったことをメモっていきます

チーム編成

毎回同じメンバー(新卒で入ったSIerの先輩・後輩)で出ており、今年は通算3回目の参加でした


チーム紹介スライドより抜粋

当日の担当は以下のような感じになりました

2021年にISUCON11に参加したときと比較して、今回はmatchasongの活躍が著しかったです
毎年メンバー固定で参加できると、各個人の業務などによる経験の蓄積が観測できたり、チームワークの改善によってうまく分業できたりするなど、継続的に続けることの重要さが感じられてより楽しめます
運営の方々への感謝の念も湧いてきます🙏

https://zenn.dev/yktakaha4/articles/postmortem_isucon11

準備

以下のことをおこないました

合同で練習

チーム用に作ったDiscordに数回集まって、過去の予選問題を解きつつ毎年改修して使っているベンチマークツールの改善をおこないました


ベンチマークツールの出力イメージ

これは主にkangaechuとmatchasongによる力作で、以下の操作を半自動でやってくれるものです

  • 各サーバーへのコードのデプロイ(git pull)
    • 差分があった場合はクラッシュさせる
  • 各種ログ(nginx / MySQL / journalctlなど)のローテーション
  • ベンチマークの実行
    • 素振りの時は、ここでベンチマークを自動実行します
    • 当日はスキップし、ISUCONポータルから手動でベンチを流したあと、スコアを入力
  • Discordへ結果の貼り付け
    • alppt-query-digestのサマリ出力
    • アクセスログや、pprofのダンプファイルをS3に格納し、リンクを表示

(自分が作ったのでないのですが、)個人的にいいな~と思っているのが、ベンチ入力後にその時点のコードにタグを打って、時刻と点数を時系列で記録している部分です
これのおかげで、結果の項に貼ったスコア推移を示すことができ、(今のところ使ったことないですが、)終盤に最高位の構成に変更するといった操作がやりやすくなっているように思います


git tagのイメージ

上位の方のアウトプットを見ていると各個人が自律的に修正・ベンチ実行をおこなっているパターンも多いように思いますが、私たちのチームはそこまで器用に進められないため、事前の段取り勝負で頑張っています

個人でやったこと

例年よりもISUCONに取り組める時間が無かったので、前回参加時にZennに書いたスクラップから役に立ちそうなものを写経しつつ、それに自動テストを追加していくということをやりました

sqlxによるややこしいSQL組み立てであったり、go-redisによるキャッシュやジョブキューといった、ほしくなった時にサッと使える関数群をまとめました

https://github.com/isushintaro/isunippets

感想として、この試みはなかなかよかったように思います

例えば、毎年参加しているとGoなり利用OSSのバージョンが相応に上がっていきますが、スニペットに自動テストを書いておくと言語やライブラリのバージョンをあげても go test で正常稼働をチェックでき、精神衛生にとてもよいです

一例として、2つのJSON(APIレスポンスを想定)を比較してどのような差分があったか出力する関数については、実務っぽくしっかり目にテーブル駆動テストを書きました
関数をテストのために利用する過程でインターフェースが整理されたり、様々なデータパターンを試して言語への理解も深まります

https://github.com/isushintaro/isunippets/blob/main/compare_test.go#L144-L214

また、プロファイリングツールのpprofを素振りで使っていて、Web UIの使い勝手に改善の余地がありそうだったのでPRを作成しました
思い当たったのがISUCON前日だったため当日利用はなりませんでしたが、もしも取り込んでもらえたら来年は自分で使いたいと思います

https://github.com/google/pprof/pull/820

当日

当日は例年通り私の家の仕事部屋に集まって、顔を突き合わせつつやりました
私たちのレベルだとリモートでやるのはチーム崩壊しそうなのと、年に一度のお祭りというのもあって集まっておこなっています


開始前の準備の様子

お昼は寿司を取るのが毎度の習わしだったのですが、今年は各自準備にして代わりに夜に羊を食べて打ち上げしました🐏

スコア推移

どんなことがあったか、箇条書きで簡単に思い出していきます


点数の推移

  • 最初の1時間はいい動きができた
    • kangaechuにてAWS環境を立ち上げ & yktakaha4とmatchasongにて当日公開のマニュアルを読み合わせ
      • 動画配信だったりDNSが構成に組み込まれているという趣旨を理解する
      • 加点条件を確認し、限られた時間の中でどこからチューニングするとよさそうかざっくり把握する
    • kangaechuがサーバ立ち上げを完了し次第チームにsshのコンフィグファイルを共有し、サーバに繋いで確認すべき作業をおこなう
      • kangaechuはベンチマークツールを当日向けに改修 & ソース一式をgithubへpush
      • matchasongはまずベンチを回して初期スコア確認 & ポータルサイト仕様の確認
      • takahashiはssh経由での稼働環境確認
        • sshポートフォワード経由でDBに接続しGolandのDatabase toolから参照できるようにする
          • 実装中のSQLにインテリセンスが効くようになってありがたい
        • ブラウザから競技用アプリケーションのフロントエンドを確認する
          • アプリケーションマニュアルを読みあわせたり、エンドポイントを辿るときにイメージが湧きやすくなる
    • 11時頃に各自のやったこととを共有
      • ここで極端に遅れていることなどがあればフォローに入ることを検討
      • アプリケーションマニュアルを再度全員で読み合わせる
        • この時に誰かが先行して読んでいると要点になりそうな部分を強調できるので、全員自立したチーム以外ではオススメできる手法でないかと思います
    • その後、徐々に各自の分業に入っていく
      • yktakaha4はアプリ、matchasongはDBインデックス、kangaechuはインフラ
  • 初動の良さがその後1〜2時間のスコア上昇に影響していたと思う
    • matchasongがスロークエリログからインデックスを一通り貼る
    • yktakaha4はアプリケーションコードをエンドポイントを起点に読んでいき、気になるところ(N+1があるとか)を FIXME でコメント→その後解消
      • インデックスが効くとよさそうなポイントもコメントで残し、matchasongのインプットにできていた
  • その後スコアは停滞気味に
    • DNSやアイコンハッシュの扱いに対する引き出しを持っておらず、細かな微改善を積み重ねる結果に
      • 例えば、インメモリキャッシュを入れるといった改善をすればよかったものを、DBに入れるためにカラム追加…のような犬の道を選んでしまった
      • DNSよりもアイコンハッシュの方が実装イメージが湧く…みたいな理由でそちらに突進してしまったのも、計測より推測を優先してしまった感がある
    • 多重のN+1が起きている関数群に対するパフォチューのような、思い切りが必要な部分への対処が後手に回って時間に入りきらなかった
  • 最後の1時間の動きに反省ポイント
    • 計測終了1時間前の時点で、その時間帯から着手できそうな弾を大体対応(ないし諦め)しきってしまい、個人的にちょっとだれてしまっていた
      • 結果、再起動試験をやってないことに気づかなかったり、pprofを無効化するといったクロージング関連の対応にいくつか漏れがあった
        • ここはチェックリストを作るといった作業をおこなうべきだと思った
    • 安全第一にしつつももう30分ぐらい粘るべきだった
      • 早めに片付けし始めたおかげで終了後すぐ打ち上げをはじめられた部分はあったので、ルールを決めて運用できるといいように思う

個人的な感想

GitHub Copilotすげえ

2022年からの差分として、GolandにGitHub Copilot が入っていたというのがあったのですが、これが大変に有用でした

https://plugins.jetbrains.com/plugin/17718-github-copilot

DBから抜いた値をJSON返却用のモデルに詰め替えるといったような、本番焦ってやるとケアレスを起こしやすいところに正解のコードを一瞬で埋めてくれて、failをだいぶ減らせたのでないかと思っています

それに対して、コードに修正方針のコメントを書いている途中は、 // FIXME: ここはN+1になってそう~ ぐらい書いたあたりで だがよくわからないため修正しない というような補完をしきりにしてきて、やわらかい気持ちになりました
自分のプロンプトの書き方が良くない可能性はあります

キャッシュの使い方の引き出しを増やしたい

スニペットを作る段階で、インメモリキャッシュとRedisを使ったキャッシュを利用する準備はしていたのですが、ファイルやDNSといったトピックが出てきた時に、そこに対して適用できるのでないか(あるいは適用するにはどのような対応が必要か)といった推論ができていませんでした

来年開催されて出ることができたら、今年の問題に対してどのようにキャッシュが適用できるかという観点で取り組めればと思います

推測するな計測せよ

今年はアプリケーションのN+1関連の問題が多かったように感じたのですが、中盤の時間帯からスコアに対するコストパフォーマンスを考えずにできそうなところに突進するムーブをしてしまったと思います

昨年も同じような反省があり今年はpprofを導入したのですが、使い方について充分に素振りできておらず、中盤からは普段通り勘で直すポイントを決めていくムーブをしてしまっていたため、計測すること、ないしそもそもコードベース全体を俯瞰して観察した上で、効果的な改修方針を推測していくような動きができたらと思いました

https://aki33524.hatenablog.com/entry/2023/08/25/231051

おわりに

実務だと、環境に最適化した行動のみ成果が出せてしまう場合もあると思うのですが、ISUCONのおかげでSWEとして引き出しや能力を増やせる部分がまだまだあると感じられるので、運営の皆さんには感謝です🙏
来年も出れたら、2桁順位目指して頑張りたいと思います💺


当日ベンチ詰まりが起きたことで高順位に位置した弊チーム


明日のアドベントカレンダーは同僚のSWEのktnytが書くようです
お楽しみに🦜

https://qiita.com/advent-calendar/2023/lapras

同じチームとして参加したkangaechuもISUCON振り返りを書いているので、よければご覧ください

https://zenn.dev/kangaechu/articles/kangaechu-isucon13

Discussion