# isucon11の予選に参加したが~~多分~~惨敗したので反省した モールの応力円で [TaKO8Ki君](https://twitter.com/TaKOBKi) と一緒に isucon11 の予選に参加しました。 [hnkz](https://twitter.com/dqn__buster) です。 去年に続いて、 isucon に参加するのは今年で2年目になります。 この記事は、isucon11 の予選に参加したが~~多分~~惨敗したので、反省をするために書きました。自分への戒めポストです。isucon12 が近くなったら、このポストを読み返して心を燃やします! ※基本、主観の出来事のみ書いています。[TaKO8Ki君](https://twitter.com/TaKOBKi)目線の出来事などは結構抜けています。 ## 前提 ### 言語 事前に [TaKO8Ki君](https://twitter.com/TaKOBKi) と話し合った結果、Rust で参戦することにしました。 UDP/IP のネットワークスタックを Rust で実装したり、ブロックチェーン上で動くDAppの開発に Rust を利用したり等、ある程度の開発経験があったため、Rust を使用することにしました。 ### 秘伝のタレ 去年の isucon10 の予選に参加した際に作ったものをベースに秘伝のタレを仕込みました。 大体の挙動は以下の通りです。 - `make install-tools`: サーバの初期設定やソフトウェアのインストールを行う - `make deploy`: ローカルでアプリをビルド後、バイナリやコンフィグをそれぞれのサーバへデプロイしてリスタートする - `make bench`: ログファイルの退避や slow-query の有効化等、ベンチを回す準備をする - `make http-anal`: [alp](https://github.com/tkuchiki/alp) を利用して nginx のログからプロファイリングを行う - `make db-anal`: pt-query-digest を利用して slow-query のログからプロファイリングを行う ## 予選前にやったこと ### 過去問演習 去年は計測と改善のプロセスをうまく回すことができなかったので、今年は予選前に[過去問](https://github.com/matsuu/aws-isucon)を解きました。過去問を解く過程で、秘伝のタレを洗練させていきました。 具体的には、isucon10の予選問題を2回、isucon9の予選問題を1回解きました。 どの回も通しで解いたわけではなく、数日間掛けて空き時間に取り組むような形でした。 また、isucon9 の予選練習では Rust の問題提供が無かったため Go で解きました。 3回予選問題をやった結果、計測と改善のプロセスが良い感じに回るようになりました。 自明なボトルネックは大体改善できるようになり、過去の予選問題ではスムーズに予選突破できそうなボーダーに辿りつけるようになったので、まぁ予選突破できるやろと思っていました。 しかし、予選問題を3回解いた段階では以下のようなことが心配でしたが、対策の時間も無かったので、不安を抱えたまま予選に臨むことになりました。 - Rust で Web バックエンドの実装をスムーズにできるかどうか - 複雑な SQL クエリを書けるかどうか - MySQL や Nginx の細かいチューニングをできるかどうか ### リポジトリ準備 前日に予選用のリポジトリを作っておき、改善作業にスムーズに入れるように以下のような手順書を作りました。 ![](https://i.imgur.com/TgAsJyw.png) ## 予選中にやったこと ### 10:00~ 僕がサーバの初期設定、[TaKO8Ki君](https://twitter.com/TaKOBKi)がレギュレーションやソースコードの確認を行います。 まずは手順書に乗っ取り、初期設定とデプロイ、初ベンチを回しました。 大体5000点くらいだった気がしています。 slow-query のログと explain を駆使してインデクスを貼り、ベンチを回すと大体15000点くらいでした。 ここまでは予選が始まってから大体1時間以内の出来事で、出だしは良い感じでした。 ### 11:00 ~ `/api/isu` が明らかにボトルネックであり、N+1 のクエリがあったので改善を試みました。 しかし、修正したクエリがJOINを多用しており、改善に失敗しました。もやもやしますが、一旦 `/api/isu` の N+1 の改善は飛ばすことにしました。 ### 12:00 ~ `/api/isu` の改善に失敗して少し焦ります。ボトルネックかどうかはあまり考えずに、`isu_association_config` が明らかにキャッシュできそうだったのでこれに取り組みます。 [once_cell](https://docs.rs/once_cell/1.8.0/once_cell/) を使ってキャッシュしようとしたんですが、実装に詰まります。`once_cell` の導入にハマった上に、実装をバグらせてしまいました。`once_cell` は使ったことが無かったのでドキュメントを読みながら実装したのですが、`Mutex` を利用するかどうか悩んだり、コンパイラに怒られたりしてだいぶ時間を使いました。実装できたものの、点数がほとんど伸びず微妙だったので、 PR はマージせずに放置しました。 ### 14:00 ~ 予選も後半に差し掛かり、本格的に焦り始めます。予想では、この時間帯には既に2〜3個の PR をマージして、点数もいい感じで、ランキング上位に居るのではとか考えていましたが、現実は甘く無かった... インデクスをいじってる時に気づいた、image を DB から切り離すやつをやることにしました。結果的に、この改善に予選が終わるまでの時間のほとんどを費やしましたが、点数は上がりませんでした。 以下のようなことに時間がかかりました。 - プログラムの実装 - 画像の保存処理のバグ取り プログラムの実装に関しては、少なくとも普段の自分の実装速度と比較して遅いなと感じるレベルの時間が掛かりました。競技中はプログラムの実装をする時、思い通りに行かないもどかしさが付き纏っていました。 ### 16:00 ~ この時間帯でも、画像分離の実装を行っていましたが、[TaKO8Ki君](https://twitter.com/TaKOBKi)からもうそろそろ冗長化構成取ってみようという提案が出たので、一旦画像分離の実装は辞めて、冗長化構成の作業に移ります。 アプリケーションの処理が重いことは`htop` コマンドをベンチ中に実行していたので知っていました。そのため、アプリケーションサーバを2台構成にすることにしました。冗長化の実装は、事前に過去問で何度か試していたため、特に詰まらずに終わりました。しかし、点数が大きく下がり(確か7000点くらい)、冗長化の実装は取り入れられませんでした。ネットワークの接続時間がボトルネックになっていたのかと思ったんですが、特に原因の特定はせずに、点数が下がった時点で変更を取り消しました。 再び、画像分離の実装に戻ります。 ### 17:00 ~ この時間になってやっと画像分離の実装が終わりました。点数はほとんど上がりませんでしたが、一つ実装を完成させられたので、落ち着きを取り戻します。そして、明らかなボトルネックである`/api/isu` に再挑戦することにしました。 高速なクエリが書けなかったので、最新の`isu_condition`のみを保持するテーブルを作成し、JOIN する回数を減らしてN+1の改善を試みました。こちらもバグらせながら実装を終わらせましたが、結果的に点数はあまり変わらず。`isu_condition` の最新情報を保持するために追加された INSERT 処理が重かったのかなと考えています。 ### 18:00 ~ 18時になると、段々と終わりを意識します。クロージングに向けた作業をしながら、ベンチを何度か走らせました。結果的に、11:00の段階でマージしたインデクスを追加する機能のみを取り入れて、終わりを待ちました。 ## 予選を経て得た反省 去年と比べて、計測と改善のプロセスはうまく回せるようになりました。しかし、反省点はいくつかあります。 ### 手に馴染む感覚を得られなかった 今回、計測と改善のプロセスを回せるように事前準備をしていましたが、SQL や Rust に関して、手に馴染む感覚を手に入れておくべきだったと思います。実装に安定感が無かったからです。isucon10 では、アプリケーションは単純なもので、DBのチューニングとテーブルを分離させる冗長化構成を取るだけで予選突破のボーダーを超えました。また、isucon9 では、自明な N+1 の改善や、単純な冗長化構成を取ることでボーダーを超えました。どちらも、Rust の実装をあまり弄ることなく、複雑な SQL クエリの改善をすることなく済みました。今回の isucon11 予選は、一言で言うと、アプリケーションのロジック改善に重きが置かれていたと思います。この対策は、正直かなり不足していました。何かの対策をする時、手に馴染む感覚(安定感とも言える?)が得られるまで対策をすることは重要だと思うんですが、今回はあまり対策の時間を割くことが出来ず、ふわふわした感覚のまま予選に臨んでしまいました。手に馴染む感覚を得るためにやることは、自分の中で大体体系化されているので、来年までに SQL や Rust を手に馴染むものにしたいと思います。 ### 見通しを立てられなかった 見通しを立てずに実装を始めた結果、ケアレスミスにより、予期せぬバグを生んだり、バグの解決に時間がかかる、点数を下げるなどの弊害が起こりました。5分でも手を止めて、見通しを立てる時間を作っていたら結果は変わっていたかもしれません。は企業のプログラミングテストを受けた際にも反省したんですが、ケアレスミスを起こしやすいという問題は、性格が起因していることもあり中々治らないものです。ルールや仕組みを設けて改善していく必要があります。例えば、以下のようなルールが考えられます。このようなルールは来年の isucon までに洗練させておきたいです。 - 改善の影響範囲と改善の手法を別の人に伝えて、承認をもらってから、改善に取り組む - 実装に詰まったら、リソースが余っている人と2人で取り組み、人が来るまで別の改善に取り組む ### その他の反省点 他にも細かい反省点はいくつかあります。以下、箇条書きにします。 - 対策が対策の意味を成していなかった - 本番に則った時間配分で対策をするべきだった - 過去問の数、特に Rust の過去問題は限られているので、より意味の対策をするための工夫が必要 - チームメンバーを3人集められなかった - 今回はお互い自分のタスクに切羽詰まっていて、競技中にチームワークを発揮する場面がほとんど無かった - 3人いれば、もう少しリソースに空きができる気がする - 焦り過ぎた - 焦った結果、計測と改善のプロセスを無視した博打に出て、改善に失敗した箇所がいくつかある - 全ての改善がうまくいくだろう、そして予選突破するだろうと思い込んでいた - 実装に詰まる時、改善に失敗した時等の、ネガティブな場面も想定しておけば、本番中の心持ちは多少変わっていたと思う ## isucon12 までにやること 来年こそは本戦行きたいので、来年までにやることをいくつか考えました。 - 個人で利用する Web バックエンドのプロジェクトは Rust を利用して実装する(※リソースに余裕がある時) - 現段階でいくつか欲しい個人用の Web アプリケーションがある - これまでは fastapi 等を使って実装していたんですが、Rust で実装に取り組む - 手に馴染むを意識suru - 定期的に競技に参加する - 競争に耐えられる精神を身につける必要がある - 競技プログラミングなどの競技に定期的に参加する(これよく言ってるけど結局続いたこと無いから、継続するための工夫を考える必要がある) - チームメイク - 来年も Rust で出たい、3人で出たい - 今年は結局人が集まらなかったため、来年に向けてチームメイクを早めにしておく - チームメンバーと交友を深めて、本番でチームワークを発揮できるようにする - イメトレ? - 様々なシチュエーションを想定して、本番に備えるという点で重要 - 見通しを立てるという点でもイメトレをする癖を付けたい ## 終わりに お疲れ様でした。なんだかんだ楽しかった〜。[TaKO8Ki君](https://twitter.com/TaKOBKi)ありがとう〜。