taka_rock’s blog

エンジニアの勉強記録

ISUCON10予選に参加してきた話

ISUCON10予選に参加してきた話

ISUCON10予選に参加してきました。 ブログを書くまでがISUCON予選?と言われているので今回はISUCON10予選について振り返っていきたいと思います。

ISUCONとは?

いい感じにスピードアップコンテスト、略してISUCONです。 その名の通り、サーバーの高速化をしたりしていい感じにスピードアップして戦います。

本選へ参加するための予選があり、下記の条件で30チーム選出されます。

  1. 一般枠 (25チーム):予選終了時スコアにおける上位25チーム
  2. 学生枠 (5チーム):学生チームの中で、予選終了時スコアにおける上位5チーム

結果は?

結果は、1122点でした。 Twitterで見た所、学生枠の5位だった方が1240くらいだったそうなので学生枠での本戦出場あと一歩でした、、、
下記が作業レポジトリです。(機密情報が含まれていた為、commitを1つに固めてます) https://github.com/reud/01NOCUSI

誰と出たの?

ワンランク上のジロリアンというチームで3人で出場しました。

メンバーの役割分担としてはそこまで明確には決めてなく、インフラとアプリケーション(Go)のコードを3人でい〜感じに分担して作業する感じでした。
Discordを使って常に通話しながらのフルオンラインで競技に参加しましたが、特に問題なく競技を行えました。

参加までの準備

事前にISUCON9の予選を用いて少し練習していました。
reudがConoHaでVPSを借りてくれたので、そこに諸々立てました。
3人ともNginx, MySQLのチューニング等したことが無かった為、皆で介抱し合いながら色々勉強していました。
VPS借りてくれたのめっちゃありがたかった、、、

  • 皆で勉強したこと
    • N+1の対策方法
    • インデックスの貼りどころ
    • New Relicを導入しての計測
    • Nginxの設定チューニング
    • Mysqlの設定チューニング
    • インスタンスを複数立てた際の設定方法
    • インメモリキャッシュ
    • Goでの非同期処理
    • etc

基本的な所の勉強&参加者のブログを読んで自分たちも実際にやってみたって感じですね。参加ブログ感謝。

当日

開始まで

10:00開始だったものの、朝起きたら12:00開始に変更になっていました。時間の余裕ができた為、10:00からDiscordに集まりながらSlackのワークスペースを作ったり、便利コマンドや困ったときの記事をSlackに貼りまくっていました。開始延期ありがたい。 そんなこんなで12:20開始に変更になり、ゆるゆると開始待機していました。

12:20 競技開始

開始と同時に、チームメンバー全員で予選マニュアルをしっかり読みました。

踏み台経由のSSHをしたこと無かったため、 ssh_configの例がマニュアルに書いてあったのめちゃくちゃ助かりました。

 Host isucon-bastion
   HostName <踏み台用IPアドレス>
   Port 20340
   User isucon

 Host isucon-server
   ProxyJump isucon-bastion
   User isucon
   HostName <自チームサーバのIPアドレス>

インスタンスに接続して最初に、ディレクトリ構成を確認しました。 Webアプリケーションは/home/isucon/isuumo/webapp以下を触れば良さそうだったのでチームメイトにGit管理の追加&Githubのプライベートレポジトリの連携等頼みました。Git管理は必須ですね。 次はNginx, MySQLのデフォルトの設定を確認したりしていました。

とりあえずベンチを走らせようと思ったもののまだ走らせる事ができなかったので、ソースコードの確認やreudがnew relicを導入してくれたりしていました。仕事が早い。

13:30

ベンチを動かせるようになったので動かしました。 493点。なるほど。

13:40

やぱったがインスタンスのメモリを確認してくれました。
2GB、、、?めっちゃ低スペじゃね??って話したりしていた記憶がある。

new relicの計測結果や、ソースコード見た感じでデータベースチューニングゲーか、、、?と強く感じてきた頃。SQLクエリとschemaとにらめっこし始めました。
インデックス貼ろうにもwhere句とORDER BYが多すぎて辛い。

14:00

マニュアルに下記の記述が書いてあったことを思い出しました。

bot からのアクセスはコンバージョンに繋がらないため、弾くことが仕様として決定しましたが、まだ実装されていません。 bot は User-Agent が以下の正規表現にマッチする形式であり、このリクエストに対して 503 Service Unavailable を返すことが許可されています。 これに対するベンチマーカーからの減点は発生しません。

やぱたが正規表現の神(正規表現エンジンとか作ってた)なので該当UAからのアクセスは503を返すようにNginxの設定をお願いしました。/etc/nginx/sites-enabled/isuumo.confに下記を追加していく感じ

if ($http_user_agent ~* "^ISUCONbot(-Mobile)?$") {
  return 503;
}

点数が上がった気がする。(確かこの頃ベンチマークのキューを積めなくてすぐに計測できなかった)

クエリ見てみるページング処理がとLIMIT OFFSETを用いて記述されている。これを良い感じにしようと思ったが、条件が複雑でどうやってページングすればよいのか分からず断念、、、(これどうやってページングするのがベストだったんですかね、、、?)

この辺で、スキーマをデータ構造に最適っぽやつに変更して、インデックスを追加してみた。(これもベンチで計測できなかった為お気持ち早くなった気がする)

Webからアプリを操作していたら、画像ファイルの配信が遅かったのでNginxで静的ファイルのキャッシュをすれば早くなるような気がしたのでキャッシュの設定を追加する。(マニュアルしっかり読んだら、「ベンチマークからのリクエストは、 API に対してのみ行われます。」と書かれているので静的ファイル周りの設定は今回は何も意味ないですね。最後にキャッシュ周りの設定を剥がしました。マニュアルしっかり読みましょう。)

16:00

reudがhtopコマンドを用いて負荷計測する事を提案してくれる。
CPU使用率が100%貼り付き、、、
処理の流れ的にもMySQLの負荷が凄い事は確実なのでMySQLインスタンスを増やそうって話になるものの17:00まではインスタンス1台でスコア上げていく方針で作業を進める。

17:00

MySQLインスタンス増やそうにも、構成どうするって話になる。

ここが今回1スコアが上がったターニングポイント

3人とも別の手法提案していてどれを採用するか結構話し合った

こんな感じでどれを採用するかなかなかまとまらない為、とりあえずGoで1台, MySQLで1台の2台構成にして計測してから考えようって話になる。

ここで、Goから別のインスタンスMySQLに接続切り替えるのに結構手間取ってしまった、、、
やったこととしては/etc/mysql/mysql.conf.d/mysqld.cnfへ他インスタンスのIPをbind-address=xx.xxx.xx.xxxで追加して、Goのインスタンスからmysql -u isucon --host=xx.xx.xx.xxx --port=3306 -pで接続できることを確認。
接続できたので問題ないだろうとWebからページの表示を確認した所500が返ってくる、、、みたいな状況になり、色々話しているうちにユーザー+ホストでMySQLユーザー権限の管理がされている事を思い出し、各インスタンスに紐づくユーザーを追加した所、問題なく接続できました。(複数インスタンスでの接続練習しておくべきだった、、、)

やっとGoで1台, MySQLで1台で動作成功し、ベンチを走らせる。
スコアは覚えていないが、あれこの構成でも結構スコア上がってね??ってなった

19:00

インスタンス構成は、色々話しているうちにreudの案を元にNginx+Goで1台,テーブル毎に分割したMySQL2台を用意して、Go側でDB2台の接続情報を持つのが一番楽&安全でスコア上がりそうじゃね??って話になった為その構成に変更。

reudが爆速でGoでの2台DB接続処理を書いてくれました。早すぎて震えた。

ベンチを走らせるとスコア881で学生順位15位くらいに食い込んで、「あれ、本戦出場目指せるんじゃね??」という雰囲気が出て、インスタンス構成はこれで確定。

MySQLinnodb_buffer_pool_sizeを2GBにしたりGo側の処理をいーかんじにしてもらい再度ベンチを走らせる。
スコア1075で学生順位7位で本戦出場が本当に見えてくる。

f:id:taka_rock:20200914035139p:plain
学生7位

20:00

ミドルウェアのログ出力をオフにしたり、new relicをGoのコードから剥がしたりと撤退作業を始めました。
諸々作業完了後に計測すると1122点。他のチームがそこまでスコア上がっていないことを願い、これ以上のスコアUPは諦めました。

21:00

終わり。

結果

学生枠でも本戦出場に食い込めませんでした、、、

振り返り

結果発表待ちの時に気づいたのですが、MySQLの設定のinnodb_buffer_pool_sizeってメモリ上に展開するデータサイズで、それ+query_cache_sizeでクエリの実行結果もどんどんメモリ上にキャッシュしていくことができるんですね、この事を知らずにメモリの空きがある事は確認できていたものの、query_cacheのチューニングを行いませんでした、、、、メモリ限界までキャッシュさせてればもっとスコアあがったと思うとかなり悔しいです。勉強不足です。(再起動テストを行っていなかったため、結局Failで落ちていたっちゃ落ちていたかも)

またスコアに繋がる打ち手を全然自分で出せておらず、あまりチームに貢献できなかったなと感じています。

細かく振り返ってみると次の点が改善できたと思っています。

  • 計測を更に行えるように
    • 計測の練習ほぼしておらず、当日ぶっつけで計測したので、更に良い計測方法があったはず、、、来年は推測せずに、計測します。
  • Goの練習を直前にもっとしておくべきだった
    • Goは書いたことがあるものの、複雑な処理を記述すのには時間がかかってしまう位のレベル感なのでGoの最適化をチームメイトに全て任せる形になってしまいました。問題点見つけて自分で爆速で修正できるレベルまで練習しておくべし。
  • ミドルウェアの設定内容を深く理解しておく
    • query_cacheの件など理解しきれていない点が存在した。細かいパラメータを完全に理解しなくとも頭の中の引き出しは増やしておくべし。

まとめ

ギリギリ、本戦出場できなかったのでかなり悔しいです。精進します。

来年の意気込み

社会人枠で本戦出場目指します。

P.S.

DISCORDに書いてあった他の人の打ち手

  • MySQL5.7 → 8に変更する
    • 8に変更すると早くなるんですね、、、普段5系ばかり使っているので知らなかった
  • generated columns
  • Special index