ISUCON 11 の予選に大学の友人と3人で出場してきました。ISUCONは今回が初参戦です。
チーム紹介
チーム名は 快適PandApex です。メンバーは
- tinax (私)
- das
- arakistic
です。全員、某大学工学部電気電子工学科の3回生です。チーム名は、das君と私が開発しているブラウザ拡張機能 “Comfortable PandA” と、3人でよくプレイしているゲーム “Apex Legends” が由来です。
はっきりと役割分担を決めていたわけではありませんが、なんとなく
- tinax -> アプリサーバーのコードの改善
- das -> nginx や db の設定
- arakistic -> dbの設定やログ解析など
といった感じで作業を分担しました。 ちなみに、他のメンバーもISUCONのブログ書いているので、ぜひ読んでみてください! - das -> https://blog.das82.com/post/isucon11q/
- arakistic -> coming soon
当日やったこと
ちゃんとメモを取っていたわけではないので、時刻はかなり適当です。
9:30~10:00
コンビニにお腹がすいたとき用のおにぎりを買いにいきました。
10:00~10:20ぐらい
- マニュアルのチェック
- gitのセットアップ
- goのpprofの設定
- mysqlのslow queryの出力
- nginxのログ設定
- phpmyadminのセットアップ
- プロンプトにgitのブランチ名を表示する設定
などを行いました。 gitについて、具体的には webapp/goとwebapp/sqlを git 管理して、githubのprivateレポジトリにアップロードするようにしました。こうすれば、ローカル環境でコードを書きかえて push して サーバーで pull すればコードを更新できるので便利です。 また、ボトルネックを探すために、go で pprof プロファイラ と mysql の slow query を有効にしました。pt-query-digest というツールも導入しようかと話し合っていましたが、練習で一度も使ったことがなかったので今回は見送りました。 DBを楽に操作できるようにしたかったので phpMyAdmin も導入しました。このとき、AWSセキュリティグループをいじって81番ポートを開けました(これが後々大変なことになります)。 それと、bash の PS1 をいじって、現在の git のブランチ名をプロンプトに表示するようにしました。ブランチ変え忘れのヒューマンエラーが減るのでかなりおすすめです。 ログを有効化する前の初回スコアは 1799、ログを有効化した後のスコアは 1636 でした。
10:20~
slow query を見ると、時間がかかっているクエリの中に
ORDER BY timestamp DESC
のようなクエリがあり、timestamp
に index が張られていなかったので index を張りました。しかし、DESC
のソートでは index が効果を発揮できない(たぶん)ため、generated column を使って -timestamp
の値が入ったコラム timestamp_neg
を作成し、そのコラムにindexを張り、クエリは
ORDER BY timestamp_neg ASC
とASCのソートに書き換えました。
かなり効果があったようで、スコアは 14184 になりました。
11:15~
GROUP BY
を DISTINCT
で置き換えたり、使えそうなところに LIMIT 1
を付けたり、N+1問題を解決したりしました。スコアは 22280 になりました。
また、私はそれと並行して、forループ内のinsertをbulk insertに書き換える改善を進めていました。
11:50
bulk insert に書き換えた結果、スコアが 24430 になりました。
11:45~
for ループの中で INSERT しているコードがあり、slow query を見ても時間がかかっていたので、tinaxがbulk insertを利用するように改変しました。効果が出たようでスコアが2000ぐらい上がりました。
13:00~
contidition
というコラムの中に文字列で3種類のデータをCSV形式で格納されているので、3つのコラムに分けて正規化しようとしました。初めは、3つのコラムに分割するように構造を改変した新しいテーブルを用意し、初期化時に元のテーブルから新しいテーブルに全データを移行するようにしようとしましたが、ベンチマークでfailが多発し、原因が特定できなかったため、PERSISTENTなgenerated columnを用いる方法に方針転換しました。しかし、逆にベンチのスコアが下がってしまい、改善する方法も思いつかなかったため、この部分の改善は諦めました。
DBを大幅にいじっていたので他の改善もできず、この1時間はほとんど何も終わってしまいました。
14:00~
上述の通り改善に失敗し、スコアを上げるアイデアも尽きてきました。スコアが伸びなくなってきたらサーバーを分けようと話していたので、DBをServer 2に移動しました。予選前に練習した甲斐があって、移行はスムーズに進みました。スコアは 36067 まで上昇して、また元気が出てきました。
15:00~
Server 3がまだ余っていたため、nginxのロードバランサ機能を用いて、アプリケーションサーバーをServer 1とServer 3に分散することにしました。DBをServer 2,3に分散することもできたと思いますが、DBの分散にはGoコードの書き換えも必要になり複雑になりそうだったため、楽にできそうなアプリケーションサーバーの分散をすることになりました。nginxの設定などで少し躓きましたが、特に変なエラーが出ることもなく、スコアも大幅に伸びて 50499 になりました。練習ではアプリケーションサーバーの分散を行なったことがなかったので、これが成功したのはかなり嬉しかったです。
15:30~
アプリケーションサーバーをさらに高速化することになりました。グラフ関係のAPIについて何も改善していなかったため、そこに着手することにしました。Goのコードを読むも、コードの意図をはっきりと理解できず、どこに高速化の余地があるのかいまいちわかりませんでした。最終的に、私の案で、グラフの生成に利用するデータの取得の範囲を制限するという、最低限の処置だけ行うことになりました。また、arakistic君の提案で dropProbability を調整しましたが、dropProbabilityを下げると逆にスコアが下がり、解決できそうになかったので、結局dropProbabilityを初期値に戻すことになりました。
17:00~
17:00にコードフリーズすることにしていたので、これ以降はGoコードの改善はしていません。ギリギリまで高速化するために
- goのechoフレームワークのログ出力をオフに
- goのpprofコードを削除
- mysqlのslow queryをオフに
- phpmyadminとapacheをstop
- apparmorをstop
などをしました。特にgoのログの無効化とmysqlのslow queryの無効化はかなり有効だったようで、スコアが1万ぐらい上がって最大 63544 になりました。ログだけでこんなに違うんですね。 その後は systemd の設定などを調整して再起動試験の対策を行いました。 再起動試験も問題なくクリアできそうなことが確認できたあとは、みんなで改めてマニュアルやレギュレーションを読んでいたのですが、ここで「AWSのセキュリティグループの設定を変更してはいけない」というレギュレーションがあることに気づきます。phpMyAdminのインストールやサーバー分離を行なったときにセキュリティグループを変更していたため、大慌てで設定を元に戻しました。これに気づいていなかったら確実に再起動試験に引っかかっていたので、かなり危なかったです。マニュアルはちゃんと読まないとダメですね…
18:45
終了!最終的なスコアは 63281 になりました。
まとめ&感想
ISUCONは初めてで、まさか予選を通過できるとは思っていませんでした。
予選中の行動を振り返ると、pprofやslow queryなどをあまり有効活用できていなかったような気がします。本戦までに、そのあたりを練習したいです。本戦まで4週間程度あるので、しっかり準備して、優勝を目指したいと思います。
コメントを残す