ISUCON(ISUCON13)に初参加してきた学び
この記事はPharmaXアドベントカレンダー2023の21日目の記事となっております。
こんにちは、PharmaXエンジニアの江田です。
今回は11月25日に開催されたISUCON13に出場してきたので、その振り返りをしたいと思います。
ISUCONとは何かに関しては、以前ざっくり記事に書いたのでそちらを見ていただければと思います。
参加したきっかけ
今年の6月に新人エンジニア向けのISUCON研修というものに参加しました。
内容としては、実際のISUCONのスケジュールに従ってISUCON形式の問題を解いていくというイベントでした。
特に事前情報なく臨んだ結果、計測ツールやアプローチ方法を1からキャッチアップしたため、時間切れで全然スコアが上がらない結果となってしまいました、、。
N+1やインデックスなど自分が普段業務で意識しているはずのものでさえ、実際に計測〜改善をするとなると手間取ってしまったというのが特に悔しく、ISUCONにでてリベンジしたいという気持ちになりました!
ISUCONは1チーム最大3人で出ることができると書いてあったので、普段勉強会をやっているメンバーで出場しました。
目標
目標はISUCONで上位20%としました。
ISUCON12予選の結果をもとに上位20%がどんなものか見てみます。
ISUCON12予選では、大体7500点を超すと20%以内に入れたみたいです。
しかし、他の回も見てみるとISUCONは回ごとに結構平均スコアが変わるので、スコアよりも各チームの解決したことを参考にするほうが良さそうと思いました。
では上位20%に到達するにはどんなアプローチが必要なのでしょうか。
ISUCONは多くの出場チームが本番でやったことをブログに書いて共有してくれています。
それらを参考にすると、大体どの回も上位20%に入るために必要そうな項目は以下でした。
- 必要なインデックスを大体貼れている
- N+1問題をいくつか解決している
- memcachedなどを用いて適切にクエリをキャッシュ化している
- サーバー分散
- +αあとひとつ(外部コマンド呼び出し・画像をDBに保存などの解決)
よって、この5つの達成を具体的な目標として設定しました!
勉強したこと
今回は、勉強時間を考えてprivate-isuとISUCON12予選過去問の2つを解くことにしました。
private-isuはISUCON本の最後まで(約30万点)、ISUCON12予選は5万点くらいまでやりました。インデックスの追加やN+1の解消、サーバー分散、キャッシュ化など目標の達成に必要な操作は大体身についたと思います。
事前準備
ISUCONではどのチームも使用するツールやgithubの導入をすぐできるように準備しているというのを見たので、自分のチームでも基本コマンドやツールの導入方法をnotionにまとめ、基本的なところでコケないよう練習しました。
以下をほぼ脳死で作業できるようまとめました。
- ソースをgithubの管理下に置く(LFS設定も)
- nginx・mysqlの初期設定
- pt-query-digestの導入・利用
- mysqlのクエリ(index、insert、trigger)
- alpの導入・利用
- systemctl・journalctl
- サーバー分散
また、当日どう解いていくかの方針や役割分担も予め決めておきました。
方針としては、最初にalpやpt-query-digestで2人が怪しい箇所を探すのと同時に、アプリケーションのソースで明らかなボトルネックを探しておき、最初に10個くらいボトルネックになりうる箇所を探そうと決めました。先に全体感を見て洗い出しを行ってからそれぞれの得意分野で分担して改善していく作戦です。
自分はmysqlとN+1回りを担当し、nginxやその他のアプリケーションの修正は他の2人に任せました。
結果
結果、やりたかったことがあまりできず、7619点で700チーム中356位という結果でした。
上位20%達成ならず、、、。
Goのソースコードの変更がずっと適用されていなかったことに最後の最後で気づき、ソースコードをほぼ変更できず終わりました。
事前においていた目標
- 必要なインデックスを大体貼れている
- ◎ スムーズにできた
- N+1問題をいくつか解決している
- △ ソースコードの変更をリセットしたため、1箇所しか手を付けられなかった
- memcachedなどを用いて適切にクエリをキャッシュ化している
- ☓ 時間が足りず、手つかず
- サーバー分散
- ◎ スムーズにできた
- +αあとひとつ(外部コマンド呼び出し・画像をDBに保存などの解決)
- △ 画像を/publicで管理する変更はほぼ完成していたが、時間がなく断念
当日の流れ
9:30 集合
普段はオンラインで3人で勉強会をしていましたが、せっかくなのでオフラインでワイワイやろうと新宿の貸し会議室を借りました。
ちょっと狭かったですが、ゲーミングチェアと光回線で作業環境としては快適でした。
9:50 ISUCONの出題を見る
youtubeのライブ配信で今回の出題内容を見ました。
出題動画のクオリティが高く、これがISUCON本番なのかとテンションがあがりました。
Vtuberの声とかどう用意したんですかね。
お題は、ライブ動画配信サイトのバックエンドサーバーの改善でした。
10:00 ISUCON開始
CloudFormationのテンプレートで環境構築を行いました。
しかし、ここで早速つまります。
次のリソースタイプは、リソースのインポートではサポートされていません:
Custom::PythonLambdaExecution
ISUCON13のポータルを見ると全く同じエラーに遭遇している人がいましたが、そこにはリージョンを東京にして実行するよう書かれていました。何度確認しても東京になっているので別要因だったと思いますが、何度か試したらここのエラーはなくなっていました。
次に発生したのは、こちらです。
The following resource(s) failed to create: [FinalInstanceIP3, FinalInstanceIP2, FinalInstanceIP1]. Rollback requested by user.
環境構築から次々問題が起き、調べても全然原因が分からなかったのでこの時点でだいぶ焦っていました。
色々考えたのちisucon12予選を練習した際にCloudFormationを使用したので、その際に作られたものが競合しているのではないかと気づき、リソースを色々削除するとやっと環境構築が完了しました。
10:40 初期設定(score: 3,731)
環境構築で無駄な時間が経過してしまったので、やっとここから初期設定を始めました。
しかし、事前に色々準備していたおかげで最初の計測まではスムーズにできたと思います。
やったこと
- 当日のマニュアルを良く読む
- github
- alp
- pt-query-digest
11:30 インデックスの追加と改善点の洗い出し(score: 6,765)
pt-query-digestをもとにインデックスを追加しながら、その間に他の2人でエンドポイントの怪しいところを探していました。
パフォーマンス・チューニングの基本は推測ではなく計測ですが、ボトムアップ的に予め改善点を洗い出しておいたほうが計測後の動きが早くなると思ったため、全体をさらっと見ました。
観点としては、ループでクエリ発行している箇所、where句の入ったクエリ、DBに画像ファイル入れてないかなど、あくまですぐわかるボトルネック候補を中心に確認しました。
インデックスを貼るだけでも点数が倍近く伸びました。
まだ改善一歩目ですが、練習がそのまま本番に生きたことですでにちょっと達成感がありました。
12:30 N+1の改善と画像ファイルのDB保存の改善(score: 変わらず)
全体の洗い出しが終わり、インデックスも大体貼れたのでN+1の改善と、DBに画像ファイルがそのまま保存されている問題の改善班で別れました。
ここからが沼でした。
N+1を改善したはずなのに、スコアが上がらない。pt-query-digestを見ても明らかに改善しようとしてる箇所がボトルネックとなっている。なぜだ、、、(Goのbuildをしていないので変更が反映されていないw)。
また、単純にN+1も何段階か関数によってネストされており、改善方針を立てるのに手間取りました。ISUCON12予選やprivate-isuではシンプルなN+1が多かったので少し戸惑いました。
初参加ということもあり、N+1の他に自分たちが全然把握できていないボトルネックがあるのではないかと頭を悩ませます。
14:30 サーバー分散(score: 7100)
他のメンバーは引き続き画像ファイルがDBから取得されている問題に取り組んでいました。
自分はN+1の解決のソースコードが一旦書き終わったので、一旦サーバー分散を先にやってしまうことにしました。手順はまとめていたのですぐ実施でき、少しだけスコアがあがりました。
17:00 Makefileを読んで自分のやらかしに気づく(score: 変わらず)
残り1時間。
サーバー分散が終わり自分も合流して3人で画像を/publicに置く処理を書きました。
しかし、何度試しても/publicに画像が置かれません。
全く変わらないのであえてエラーで落ちるような処理を入れてみましたが何食わぬ顔で動作します。
ここで初めて、あれ?ソースコードの内容変更されていないんじゃないか、ということに気づきます。
DockerfileやらMakefileやらを見て起動時の処理を追っていくと、Goのビルドが自動で行われるような仕組みがないことに気づきました。
ISUCON12予選がdocker-compose.ymlを使用していたのですっかりその感覚になっていましたが、今回はmake buildでビルドしないとずっと最初のバイナリファイルが読まれるのでsystemctlで再起動しても変更は反映されません。マニュアルにはなかったので普段からGoを使っている人からしたら常識なのでしょうか。
17:10 最後の足掻き(score: 7420)
やっとソースコードが変更できるようになったので、最後の足掻きとして今までのN+1の改善のコンパイルエラーを通し、整合性が合うよう修正していきました。大きなものは修正する時間が足りないと判断し、小さな改善だけ着手しました。
多少スコアがあがりました。
17:30 画像を/publicに置く処理の続き(score: 7619)
途中まで書いていた、画像を/publicに置く処理を残り時間で完成できないか頑張ります。処理自体は完成しましたが、整合性チェックでうまくいきませんでした。
最終スコアを落とさないため17:50には断念し、スコア7619で終えました。
ベンチマークは予約してしばらくしてから実行されるので、早めに切り上げざるを得ませんでした。
ISUCON13に出場して
良かった点
- 事前にまとめていたドキュメントで、初期設定やサーバー分散がすぐに完了した。
- 事前にどう動くかまで認識をあわせたことで、スムーズに分担を分けられた。
- 予め目標とそれに必要な知識をみんなで認識していたため、モチベ高く勉強できた。
- 上位20%には入れなかったものの、50%近くまではいけた。
- オフラインでコミュニケーションを取りながら楽しくやれた。
学び
- N+1の改善に少しでも手間取った時点でmemcachedの使用を視野に入れれば作業時間を短縮できたかもしれない。
- マニュアルだけでなく、各サービスの起動の仕組みまで良くコードを読んでおく。
- スコアが下がることを恐れ、練習でやらなかったやり方を取ることをしなかった。もっと柔軟にやる必要があるので、過去問をもっと解くなどで解法の引出しを増やしたい。
- Goに慣れておらず、文法自体に脳のリソースを使ってしまった部分があった。その場のノリでGoにしたが、普段から業務で使っているrubyであればもっとスラスラ書けたのでもっとメンバーで議論しても良かった。
最後に
ISUCONはお祭り感があってすごく楽しかったのですが、まだやれたの気持ちのほうが大きく、メンバーと悔しがっていました。メンバーとも悔しいからがっつり勉強して来年も出ようと話していました。
出場したからこそ見えた学びを活かし、来年こそは目標を達成したいです!
PharmaXエンジニアチームのテックブログです。エンジニアメンバーが、PharmaXの事業を通じて得た技術的な知見や、チームマネジメントについての知見を共有します。 PharmaXエンジニアチームやメンバーの雰囲気が分かるような記事は、note(note.com/pharmax)もご覧ください。
Discussion