この度、前職同僚の joe に誘われて、同じく前職同僚の mpyw と一緒に ISUCON12 に参加しました。備忘録として本記事を残します。
本記事では ISUCON12 の技術的な解説はしません(というよりできません)。凡人が誰にでもできるような当たり前の技術で ISUCON をいかに生き抜いたかにフォーカスを当てた記事になります。
mpyw が技術面の記事を書いてくれました!(2022/07/25 追記)
ISUCON 12 初参戦レポート
joe が参加レポートの記事を書いてくれました!(2022/07/29 追記)
ISUCON12 に参加してきました
7月23日開催!ISUCON12 オンライン予選ライブ中継のイベントを公開しました🚀ライブ中継を見ながら、予選参加チームの皆さんを一緒に応援しませんか?お便り紹介コーナーなどの企画も準備中📨ぜひあなたの熱い応援メッセージをお送りください。#isuconhttps://t.co/fIh2WWHpsP
— ISUCON公式 (@isucon_official) July 13, 2022
目次
参加メンバー
参加メンバーは前述の通り前職の同僚の集まりでしたが、実は 2 人とはチームが分かれていたため、一緒に開発をしたことはありません(joe と mpyw は同じチームだったはず)。ただ、社内での交流は活発であり退職後も連絡を取り合う仲ではあったのでコミュニケーションに関して特段不安はありませんでした。
強いエンジニアたち
ISUCON はインフラ強いやつ1人は必須
— 𝐂𝐓𝐎 (𝐂𝐡𝐢𝐞𝐟 𝐓𝐰𝐢𝐭𝐭𝐞𝐫 𝐎𝐟𝐟𝐢𝐜𝐞𝐫) (@mpyw) July 23, 2022
あとはGoとRDBに強いやつ1人か2人
joe はインフラのスペシャリストです。ISUCON に必要な AWS 周りの操作はお手のもので、さらに今回の計測で使用する New Relic のスペシャリストでもあります。「インフラに強いやつ」に該当します。
mpyw は言わずとも知れた PHP 界隈の有名人であり、バックエンドや RDB のスペシャリストです。Qiita や Zenn の記事で見たことがある人も多いのではないでしょうか。PHP だけではなく Golang も実務レベルで実践しており「Go と RDB に強いやつ」に該当します。
では私は…?
凡人の私
私はそもそもフロントエンドエンジニアであり ISUCON には不向きです。一応バックエンド(Node.js, Python)の実務経験はありますが、バックエンドを生業としているエンジニアからすると確実に腕は劣ります。Golang に至っては読んだことすらありません。インフラに関しては個人サービスを建てられるレベルでしかなく、サービスの運用経験は皆無です。私不要では…?
凡人が生き抜くために
ISUCON で役立つスキルセットを持ち合わせていない私には誰もが持っている業務スキルで戦っていくしかありません。私の持つ業務スキルではチームマネジメントが今回活用できそうだったので、これを軸に戦っていきます。
自分のロールをはっきりさせた
「インフラに強いやつ」でもなければ「Go と RDB に強いやつ」でもない私が彼らと同じ戦場に立つためにはロール(役割)を明確化する必要があります。今回は joe がインフラを、mpyw が Go と RDB を担ってくれるという割り振りだったので私はそれ以外の全て担当することにしました。
具体的には**「開発者には開発に集中してもらう」**をテーマに、以下の点を自分のロールとして設定しました。
- チームマネジメント
- 開発以外の雑用
ロールをこなす上で意識したこと
開発者には開発に集中してもらう
joe にはインフラ、mpyw にはアプリケーションに集中してもらうために、私がレギュレーションやスコア計算方法などのルール把握に務めました。また、デプロイフローなどが自動化されておらず人力でやる必要があったので、そちらも同時に担当しました。
全体を眺める
競技中の開発者は目の前の課題を解決することに集中します。そのためには時間管理やタスク管理などの全体を見ることをせず作業に没頭する必要があります。そこで課題に取り掛かっていない私がそれらの全体管理を担いました。
一歩引いた目線でレビューする
競技中の開発者は目の前の課題を解決するために、どんな手が最善なのかロジックを組み立てて実装を行います。彼らはパフォーマンスを改善するためにチューニングされたコードを作ることに注力します。そこで私は別観点としてコードが仕様上の互換性を保っているかに注力してレビューを行いました。
並列作業が可能な状態を作り出してチームのパフォーマンスを上げる
3 人チームで別々のロールが割り当てられているので、できる限り分担して作業ができるようタスクの割り振りを行いました。また、作業自体がコンフリクトしないようにブランチを分けてデプロイ作業を行いました。
真っ先に自分を疑う
私が担っている多くの操作はヒューマンエラーが起こりやすいものでした。例えば実装されたコードがうまくデプロイされていなければ、開発者は存在しないバグと戦うことになります。そうならないために自分の操作を真っ先に疑い適切にデプロイされていることをコミットログや実際のコードから確認しました。
今日 #isucon でチームメンバーから指摘されたやつ
— 𝐂𝐓𝐎 (𝐂𝐡𝐢𝐞𝐟 𝐓𝐰𝐢𝐭𝐭𝐞𝐫 𝐎𝐟𝐟𝐢𝐜𝐞𝐫) (@mpyw) July 23, 2022
「@mpyw のコミットメッセージ全部 wip だから git pull 成功したのかどうか分からない」
(確認しづらいのでコミットログを wip で埋めるのは勘弁して)
タイムライン
時刻 | スコア | 施策 |
---|---|---|
開催前 | - | 事前練習 |
9:30 - 10:00 | 開会式 | |
10:00 - 11:00 | 3000 | 環境構築・計測・方針決定 |
2000 | New Relic 導入 | |
11:00 - 11:30 | player_score に index 追加 | |
11:30 - 12:00 | ランクのページネーション処理を SQL に移行 | |
slow query 有効化 | ||
Makefile 作成 | ||
12:00 - 13:00 | retrievePlayer の N+1 問題の解消 | |
3000 | ランク参照時の visit_history への書き込みを非同期に | |
13:00 - 15:00 | player_score 挿入時に duplicate key を適用 | |
5000 | player_score 挿入を bulk insert で行うよう変更 | |
7000 | RDB 周りのインフラ構成を最適化 | |
15:00 - 15:30 | flock 削除 | |
9000 | システム全体の ID 採番に ULID を使用するよう変更 | |
オンライン予選ライブ中継にチーム名が載る | ||
15:30 - 16:30 | 0 | テナントの課金計算を最適化 |
16:30 - 17:30 | 0 | ログ削除・インフラ最適化 |
17:30 - 18:00 | 7000 | 壊れたインフラの復旧 |
事前練習
事前に ISUCON 事前講習 2022 座学 を開催しました(資料と動画と問題あり)の資料や動画を読み込んだ上で必要な情報を Gist にまとめました。また 7/16(土)には ISUCON11 の練習会をチームで行い、実際の問題の傾向や本番での流れを掴みました。
開会式からの方針決定
10:00 - 12:00
開会式では今回のミッション指令を下すカッコいい動画が流れ、サービス概要と改修ポイントの説明、そして 8 時間後にリリースを控えている炎上案件である旨が伝えられ競技が開始しました。
joe が CloudFormation を立ち上げてサーバーにアクセス、mpyw がコードをローカルにコピーしてリポジトリを作成、私がマニュアルを読んでサービスにアクセスできる状態にしました。一通りの環境再現が終わると joe は初回計測と New Relic の導入および Makefile の作成、mpyw はコードの流し読み、私はスコア計算方式を読解とそれぞれの作業に取りかかりました。
スコア計算方式や New Relic の計測結果、開会式でのヒントを元にランキング API の改修から取り掛かる方針で合意し開発を開始しました。
ベースラインを行ったり来たり
12:00 - 13:00
ある程度開発が進んだ段階で mpyw から「このテーブルって MySQL だっけ?」という質問を受けました。これに対して私はろくに確認もせずに「多分そうだと思う」という返答を返してしまい、彼は SQLite に存在しないクエリを発行して Syntax Error に頭を悩ませていました。そこに昼食から帰宅した joe が「そのテーブル SQLite だよ」と一言つぶやいたことで状況は一気に解消しました(本当にすみませんでした)。
怒涛の快進撃
13:00 - 15:30
ここから mpyw と joe による怒涛の開発が進みます。mpyw は SQL 周りの問題があるコードを修正してコミット、 joe が RDB 周りのインフラ構成の見直しを行うことでスコアはベースラインからぐんぐん伸びていきました。それに伴い順位も着実に伸びて、ついにオンライン予選ライブ中継に名前を載せるまでになりました。
このあたりで mpyw から「SQLite を MySQL に移行したい」と提案を受けますが SQLite のデータ保管方法と練習でデータ移管を行わなかった観点から時間内に成果を出すのは限りなく厳しいだろうと判断して断念しました。
そして散る
15:30 - 16:30, 16:30 - 18:00
テナントの課金計算の最適化を行っている最中、データ起因と思われる不具合で検証が通らずにスコア 0 の状態が長く続きました。16:30 になった時点でスケジュール的にはログの引き剥がし等のインフラの最適化を行わなければならず、こちらの修正を続けることを断念しました。
私の悲鳴(マージできなかった) #isucon pic.twitter.com/2HIa82GULf
— 𝐂𝐓𝐎 (𝐂𝐡𝐢𝐞𝐟 𝐓𝐰𝐢𝐭𝐭𝐞𝐫 𝐎𝐟𝐟𝐢𝐜𝐞𝐫) (@mpyw) July 23, 2022
17:00 頃に joe が作業を終えて計測を回してみると動かない。ここから再起動試験を諦めて 1 コミットずつ地道に checkout しながら破損したポイントを探すデバッグ作業に入りました。
私は最後にギリギリ失格になるぐらいでインフラ壊したの札を下げています
— joe (@joe_yuzupi) July 23, 2022
デバッグが完了して切り戻しを行った時点で時刻は 17:50 になり、最後にインフラのちょっとした修正コミットを重ねて競技終了を迎えました。再起動試験はできないままでした。
結果
初めての ISUCON は、残念ながら失格という結果に終わりました。 詳細を聞いてみたところ、初期化リクエストに失敗し 502 が返却されていたとのことです。おそらく再起動時にインフラ起因で不具合が発生したものと思われます。
ふりかえり
総括として、途中までは良い動きをしていたと思いますが、終盤で崩れてしまいました。また、全体的に私のタイムスケジュールの管理が甘かったと思います。実は練習では再起動試験やインフラ最適化まで至らずに解散してしまったので、そのあたりに割く時間をもう少し多めに取っても良かったかも知れません。
よかった点
チーム内で分業ができていた
インフラ、アプリケーションの分業ができていたため、待ち時間やコンフリクトがほとんど発生しませんでした。また、コード push 後の動作確認および計測周りも含めて分業されていたので開発者が開発に集中する環境は作れていたと思います。
早い段階で計測を行い戦略を立てた
競技の初期段階で手早くツールやベンチマークによる計測ができたことは戦略を決める上で大きな鍵となりました。特に New Relic による API の計測の威力は凄まじく、開発イテレーションに大きく寄与していたと思います。これにより「やるべきこと」と「やるべきではないこと」の区別が明確についたと思います。
Golang を選択した
ISUCON は Go 以外使う気全く無し
— 𝐂𝐓𝐎 (𝐂𝐡𝐢𝐞𝐟 𝐓𝐰𝐢𝐭𝐭𝐞𝐫 𝐎𝐟𝐟𝐢𝐜𝐞𝐫) (@mpyw) July 23, 2022
ISUCON 11 の練習のときから決めてたけど今回さらにそう思った
ISUCON に有利であるということで mpyw の得意言語である PHP ではなく Golang を採用しました。実際に素の状態の Golang でも PHP とのスコア差は歴然でしたし Goroutine でスコアが上がった部分が大きかったです。ISUCON12 オンライン予選の利用言語比率を見ても圧倒的に Golang が選ばれています。
ブランチ運用が(途中まで)うまく行っていた
未計測の施策は master にマージせずに別ブランチで行っていたため、切り戻しを容易に行うことができました。しかし、最後のインフラ最適化では「インフラだし大丈夫か」という甘い考えで master に直接 push することを許可してしまい、不具合の切り戻しに時間がかかりました。
— 𝐂𝐓𝐎 (𝐂𝐡𝐢𝐞𝐟 𝐓𝐰𝐢𝐭𝐭𝐞𝐫 𝐎𝐟𝐟𝐢𝐜𝐞𝐫) (@mpyw) July 23, 2022
反省点
事前準備が不足していた
実際に ISUCON に参加してデプロイフローの自動化や計測周りの作り込みの甘さを実感しました。特に計測するメトリクスを事前に決めていなかったため、競技終了後の反省会でログ監視ツールが欲しかったなどの意見があがりました。
引き際を見誤った
1 時間かけて成果をあげられなかった「テナントの課金計算の最適化」に関して、泥沼なデバッグに入った時点で開発を停止して他の施策に着手するべきでした。もしかしたら mpyw ならやりきってくれるかも知れないという一縷の望みを捨てることができずに開発を停止しなかった結果、インフラをチェックする時間が減ってしまったことで最後の不具合につながってしまったと考えています。サンクコストに負けずに撤退を決める難しさを改めて実感しました。
1 コミット 1 計測 を守れていなかった
事前の取り決めで「1 コミットにつき 1 回計測を回す」という合意がなされていましたが、私が上記の「テナントの課金計算の最適化」の壁打ちをしていたため計測が滞り、結果として joe が複数コミットを同時に反映させてしまい不具合が発生しました。引き際を見誤らずにデプロイと計測をコミット単位で行っていればもう少し早く不具合に気づくことができたと思います。
時間の見積もりが甘かった
タイムスケジュールとして 16:30 になったらログ削除等のインフラの最適化を行うということは決めていたのですが、事前練習の段階でこのタスクや後続の再起動試験をやっていなかったことを加味するべきでした。また開発に関してもタイムボックスを決めずに行っていたので時間による施策撤退の判断ができていませんでした。
私の力が不足していた
初めて触った Golang もですが RDB やインフラの基礎知識が抜けているなど力不足があり、壁打ち相手やコードレビューの観点が抜け落ちることが度々ありました。また、ロールとしていたはずのマネジメントではタイムスケジュールを意識しつつもタイムボックスを意識した開発を行うことができなかった点において未熟さを痛感しました。
まとめ
チームとしては総じて練度不足による時間不足が否めなかった
- 特に練習していなかった終盤での手際が悪くハイスコアを殺す結果になってしまいました
- 逆に練習で動きを確認していたコーディング部分に関しては比較的成果を出せました
私個人としては力が不足していた
- Golang, RDB, インフラの壁打ち相手として力が不足していました
- マネジメントをするならタイムボックスを決めるべきでした
感想
正直言うと記念出場くらい気持ちだったので、速報値とはいえオンライン予選ライブ中継に名前が一瞬載ったのは嬉しかったです。全く歯が立たないかと思っていましたが、チームメンバーのおかげで手応えを感じることができました。結果としては残念な形になってしまいましたが、個人的には楽しめたと思います。
こんな凡人の私を誘ってくれた joe と暖かく迎えてくれた mpyw には感謝します。一緒のチームで戦ってくれてありがとう!また機会があったら頑張りましょう!