はじめに
「がんもどき」(@sappi_red, @ryoha, @temma)というチームでISUCON10予選に参加して、全体5位・学生2位で予選突破しました。
全員、初出場でしたが練習のおかげでスムーズに作業できてよかったです。最終スコアは 3407 でした。
構成
仕様実装: Go
- 1台目: アプリケーション+Nginx
- CPU Usage: 40~60%, memory: 20~30%
- 2台目: estateテーブル用DB(mysql 5.6)
- CPU Usage: 95~100%, memory: 30%前後
- 3台目: chairテーブル用DB(mysql 5.6)
- CPU Usage: 80~95%, memory: 30%前後
やったこと
準備
部内WikiのISUCON攻略記事やこれまでのwriteupを読んだりしながら、部内ISUCONを10万点まで触りました。あとは、Makefile書いたり、リポジトリ作ったり、攻略をMDにまとめたりとほそぼそ準備をしてました。
Makefileには初動のセットアップ周りと計測周りが書いてあります。僕は結構ポカをやりがちなのでsetupコマンド以外で必要な操作をechoで色付き出力するようにしておきました。
役割分担は
- @temma
- インフラ担当
- 主に計測とDB, Nginx周り
- @sappi_red
- アプリ担当
- 実装力が高いので複雑な改善をやる
- gitで困ったとき頼る
- @ryoha
- アプリ担当
- N+1の改善やINDEXをやる
- とりあえず実装を頼む
みたいな感じでした。
当日
結局5:30ぐらいまで寝れなくて、朝めっちゃしんどかったです。1億年ぶりに朝ごはんとかいうの食べました。
誰かの家に集まるか迷っていましたが、ディスプレイが多いほうが良いってことと通話での作業に慣れていたことからDiscordとSlackでリモート参戦することにしました。
10:00
12時からになったので、とりあえず集まって初動とか全体の流れの話をしてました。あとは、レギュレーションの再確認やGeneratedColumnの書き方とかを確認しつつ雑談タイムという感じです。
12:20 競技開始
アクセスが集中したせいかteamID等が確認できなかったので、とりあえず開けたmanualから読むことにしました。
少ししたらportalが開けるようになったので、他の二人にはmanualやコードを読んでもらいながら
- ssh configを書く
- 構成の確認
- 各種git管理
- ツールの導入
などを進めました。この間に、コードを読んでいる二人はTODOをコメントで追加してくれていました。
色々やって、とりあえず改善を入れていける状況になるまでで大体1時間程度でした。
一旦体制が整った13:15分時点の初回ベンチでスコアが 482点 です。
13:50
この時点で@ryohaがpostChair
のbulk insertを実装して、ひとつN+1を解消してくれていたのですが、ベンチマーカーが回せないとのことだったのでそれ以外のエンドポイントを触ることにします。
manualを読んだ時点で、@sappi_redがbotをブロックする方法を思いついていたのでrobots.txtとnginxでのブロック($http_user_agent
)を導入しました。
他には、@ryohaがsearchChairs
のsearchCondition+limitOffset
付きの重いクエリが2回飛んでいるところをmysqlの情報関数を使って軽くしてくれていました。
14:50
ベンチマーカーが回せる様になったのでこれまでの改善を修正しつつ、改善を加えていきます。
計測
計測には以下のツールを利用しました。
- pprof/fgprof
- 序盤のボトルネックがはっきりしている状態ではfgprofを利用します。今回は大きなボトルネックを取り切れなかったのでpprofは使用しませんでした。
- mysqldumpslow/pt-query-digest
- 基本的にはmysqldumpslowを使用し、クエリの詳細が知りたいときにはpt-query-digestを使用しました。
- kataribe
- nginxのログ解析に使用します。ここを最初に見る事が多いです。
- htop
- リソースの状態を監視するのに使用します。常に起動していました。
- dstat
- リアルタイムでのリソースの状態を監視したいときに使います。
- mysqlプラグインを利用して飛んでいるクエリの数や種類を監視したりできるのでそれも合わせて使用しました。
これらをMakefileのコマンドから実行し、結果をslackcatでslackに送信するようにしていました。
ここまでの改善でスコアが 1219点 に伸び、加えて以下のような改善後のスコアが 1427点 でした。
searchRecommendedEstateWithChair
の物件取得の改善(@sappi_red)- INDEXの追加(@ryoha, @sappi_red)
- featuresへのFULLTEXT INDEXの追加(@sappi_red)
- DBを2台目に移行(@temma)
- nginxの秘伝のタレ(@temma)
小話: @sappi_redがfeaturesをSET型でDBに入れようとしていたのがずっとfailしていて、ベンチのreasonも不具合で返ってこなかったので運営に問い合わせたら、DBに入っている順を保持して返してほしいとのことでFULLTEXT INDEXを使うことにしました。DBに入っている順を保持して返す意味がよくわからなかったので有識者は教えてくれると助かります。
18:00
ここまででなんだかんだ結構改善を入れていたのですが、思いの外、DBのCPU使用率が改善せずボトルネックのままでした。
INDEXの効きが悪いように感じたので@sappi_redに頼んで実行計画を見てもらいながら、裏でtable毎にDBを分けることにしました。tableを同時に参照するような処理がなかったからです。DBを2台構成にしたタイミングで2500点弱出るようになり、@sappi_redが凸包の内外判定をアプリ側で行う処理を実装したことで3000点近く出るようになりました。
19:00
1時間前にコードフリーズすることを決めていたので、残り1時間です。
@sappi_redが改善してくれたはずのINDEXですが、いまいちスコアに反映されなかったのでestateのrange_id
系の判定をGenerated Columnを使って高速化できないか試してみました。時間がないので、@ryohaにアプリ側のコードを書いてもらいつつsqlを書いていました。
裏では@sappi_red自作の超速JSONエンコーダーを@sappi_redが導入してくれたのですが、他がボトルネックすぎてスコアが伸びなかったので全部revertしました:sad:
若干スコアが伸びたもののあまり効果はなかったようです。ほかのところでINDEXを貼るために使うと良かったみたいですね。初めて使う機能だったので、最初に手を出して沼るのが怖くてなかなか発想に登りませんでした。割とスッと導入できることが分かったので、本戦ではうまく使っていきたいです。
19:40
時間的にほとんどできそうなことが残ってなかったので、最後に@sappi_redがNginxのUnixDomainSocket利用を試してくれましたがやっぱりうまくつながらなくて断念です(試したことはあったけどうまくつながらなかった)。
DB側のメモリが余ってたので、パラメーターをちょこちょこいじってましたがあんまり改善しなかったです。キャッシュヒット率とか求めてなかったのでMySQLTunerとかちゃんと使えばよかったなという感じです。
20:00
コードフリーズして再起動試験を行いました。@sappi_redが設定を戻し忘れててオイオイ落ちとるやん〜〜みたいな事があったのでちゃんと再起動試験してよかったです。
あとは、ログとか外してガチャタイムです。2
実は、最後の方のベンチガチャで「レスポンスが不正です」でまれにFailすることがあったのですが、アプリケーションのコードに不備を見つけられなかったので、ベンチマーカーの不備であることを祈ってました。
学生1位だと思ってたのでshallow verseが発表されたときはマジで絶望しましたが無事通ってて嬉しいです。
まとめ
改善に関してはINDEXのチューニングが甘かったこと以外はずっと沼っているとかもなく、及第点という感じです。
コロナ期間、この三人でずっと通話していたのでそれぞれ何が得意か共有できていたのと全体的にチューニングに関する知識を共有できていたのでコミュニケーションコストは小さかったと思います。(「これ多分例のアレで悪いからあの手法で直しといて」みたいなのが通じて良い。)
本戦では学生1位を目指してがんばります!!
P.S.
がんもどきはto-hutohuさんの豆腐を原料に(ぐちゃぐちゃにして)できているってことから名付けました:love: