ISUCON11予選に参加して失格になってきた
概要
去年(予選, 本戦)に引き続きISUCON11 予選に参加し、失格になってきた。
その振り返り。
また、去年参加したチームは、今年社会人になった僕以外今年も学生ということで、学生枠での本戦出場を目指して解散した。
ということで会社の同期に声を掛けて、次のチームで出場した。
事前準備したこと
利用言語は、メンバー全員が普段業務でKotlin, ScalaといったJVM系の言語を書いており、実装が無いので可読性の高さなどからGoでやることにした。
ミドルウェアはNginx, MySQLが使用されると想定して、設定や計測の練習や準備を事前に行った。
準備した計測ツールは下記
- プロファイラー
- pprof
- アクセスログ解析
- alp
- スロークエリ解析
- pt-query-digest
- リソース確認
- htop
毎年ツールの導入方法をISUCON前に思い出しているので、自分のメモのために導入方法も書いておく。
pprof
net/http/pprofに書いてあるように、こんな感じで追記すればOK。便利。
import _ "net/http/pprof" func main() { go func() { log.Println(http.ListenAndServe("localhost:8080", nil)) }() }
プロファイルする際はローカルから
$ go tool pprof -http=":8888" "http://example.com:8080/debug/pprof/profile?secondos=60"
みたいな感じで、計測をした。
alp
下記コマンドでインストール
$ curl -OL https://github.com/tkuchiki/alp/releases/download/v1.0.7/alp_linux_amd64.zip $ unzip alp_linux_amd64.zip $ sudo install alp /usr/local/bin/alp
Nginxのログ出力をLTSV形式に設定して、こんな感じのコマンドでアクセス合計時間で降順にソートしてログを解析できる。
$ alp --sort=sum -r --file=/var/log/nginx/access.log ltsv | head -n 10
pt-query-digest
下記コマンドでインストール
$ wget https://www.percona.com/downloads/percona-toolkit/3.0.3/binary/debian/jessie/x86_64/percona-toolkit_3.0.3-1.jessie_amd64.deb $ sudo apt-get install -y gdebi $ sudo gdebi -n percona-toolkit_3.0.3-1.jessie_amd64.deb
MySQLの設定でslow_queryを出力するようにして、下記コマンドで解析できる。
$ pt-query-digest /var/log/mysql/mysql-slow.log
htop
topコマンドで もリソース確認できるが、特に何もしなくても見やすいのでhtopを使用することにした。
$ sudo apt install -y htop
でインストールして
$ htop
で使用する。
セットアップスクリプト
当日、各サーバーにセットアップする手間を減らすために簡単なスクリプトを書いた。
デプロイスクリプトも書きたかったが、準備する時間が無く、準備できなかった。絶対にあった方が便利なので、来年は頑張りすぎない程度に事前に用意する。
当日やったこと
当日のチームメンバーの動きは、下記のように予め大まかに決めておいた。
- takahiro
- インフラ
- tomonori
- 問題の読み込み+計測+App改善
- shutarou
- App改善
当日行ったことを、自分が行ったことと記憶を元におおまかな時系列順にまとめていく。
10:00 競技開始と初回ベンチ2396点
競技開始され、AWS CloudFormationのテンプレートが配布されたので適用した。
競技用サーバーのIPが確定したら、ssh用のconfigファイルを用意してメンバーに共有した。
ここで実行した初回ベンチマークのスコアは2396点
だった。
10:10 諸々のセットアップ
事前に用意しておいたセットアップスクリプトを実行し、tomonoriに問題の読み込み、shutarouに競技のコードをGitHubへのpushをお願いした。
自分は、サーバーのスペックと実行環境を確認した。
サーバーのスペックはAWSコンソールから確認し
$ sudo systemctl status nginx
でnginxが動作してることを安心して
$ sudo systemctl status mysql
でMariaDBだったことに少し驚いたが、MySQLと互換性あるからなんとかなるだろうと思い、alpとpt-query-digestで計測できるようにNginxとMatiaDB側の設定を書き換えた。
10:30 AppとDBの分離
AppとDBを別のサーバーに分けたほうが計測しやすいと考え、最初に下記の様にAppとDBを別サーバーに分離した。
- サーバー1
- Nginx
- App(Go)
- サーバー2
(限界まで1サーバーでチューニングした方が良い気もするので、来年は事前の準備段階でそのことについてもうちょっと考えておいた方が良さそう。)
ここで、ローカルからもDBへアクセスできるように、AWSのセキュリティーグループのインバウンドへ3306を特に何も考えずに追加した。
11:00 MariaDBのチューニングを少しして6825点
MariaDBのチューニングを少しした。
git管理していなかったので微かな記憶だが、下記の値を設定した気がする。
(来年はこの辺の設定ファイルもちゃんとgit管理しておきたい)
innodb_buffer_pool_size = 3GB innodb_flush_log_at_trx_commit = 2 innodb_flush_method = O_DIRECT
サーバーのメモリが大きかったので、雰囲気でquery_cache_size
も大きくしといた気がする。
この時点で、スコアは6825点
12:00 DB使用率が100%
ベンチマークを実行した際のサーバーリソースを計測すると、DB使用率が100%だった。
alpの実行結果と
pt-query-digestの実行結果
を確認して、
isu_condition
テーブルのreadとwriteが原因でCPU利用率が100%になっていると考え、POST /api/condition/[0-9a-z\\-]+
があまりにも多かったので、write側のチューニングをしようとチームで決めた。
(pt-query-digestの結果から、INDEXを貼ることによりreadの改善が期待されたが、write優先で後回しにした。パッとできる改善ではあったので、パッとやっちゃえば良かったと後から思った。)
12:30 isu_conditionのBulkInsert化
他のメンバーがisu_condition
のwriteを減らすために複数回分のリクエストをまとめてInsertしようとしたりしてくれていたが、非同期周りのハンドリングが難しくて、1リクエスト内のBulkInsertだけ実装した。この辺はチームメンバーに任せていたので、詳しいことは分かっていない。
その間に自分は、Appのソースコードを読んだり、DBの中身を確認してた。
13:30 GoからDBへのコネクションの制限を無くす
ソースコードを読んでいたら
db.SetMaxOpenConns(10)
と、最大コネクション数が10に制限されていたので、コメントアウトした。
スコアは全然上がらなかった。
14:00 jia_service_urlを変数で持つように
isu_association_config
に保存されているjia_service_url
が、初期化処理時以外に変化して無さそうだったので初期化処理時に変数で持つようにしてDBから取得しないようにした。
これもスコアは全然上がらなかった。
14:30 DBを2台にしようと妄想する
相変わらず、DBサーバーがCPU100%のままなのでなんとかしようと考える。
水平分割すると良いのかな?とか妄想しながらとりあえず、ボトルネックになっているisu_condition
テーブルだけ別サーバーのDBにしてなんとかしようとGoとDBのセッティングをしてた。
↓が起きたので、PRだけ作成して反映はしなかった
15:00 isu_conditionに貼り忘れてたINDEXを貼る 23000点
isu_condition
にINDEX貼ると良さそうって話をしていたもののINDEXを貼っていなかったので、tomonoriがINDEXを貼ってくれた。
ベンチマークを実行すると唐突に23000
点になって衝撃を受けた。
サーバーのリソースを確認するとDBのCPU使用率が低下し、App側が100%になっていたので、サーバー構成を
- サーバー1
- Nginx
- App(Go)
- サーバー2
- App(Go)
- サーバー3
にしようと考える。
16:30 App2台構成で30000点
Nginxでロードバランシングすれば、App2台に簡単に構成にできると思ってたら、少しハマってベンチマークが失敗し、時間を取られてしまった。
ハマった点としては下記
- 初期化に必要なSQLファイルが追加したサーバーに配置されていなかった
- 初期化に必要な
/sql/1_InitData.sql
をgitignoreしており、元々存在していたファイルを削除してしまっていたため、初期化に失敗していた。全然意識していなかったので、気づくのに遅れてしまった。
- 初期化に必要な
- jia_service_urlを取得できない
- jia_service_urlを初期化時に変数で持つようにしたことにより、初期化をしていないサーバーだと失敗するようになっていた。
上記を改善して、App2台構成で30000
点
17:00 trend APIの改善をしようとする
App2台構成で負荷分散すると、DBのCPU使用率が100%になった。
alpの結果が
とtrend APIの呼び出し回数が、増えていたので他のチームメンバーに改善をお願いした。
任せてしまったので、詳細な変更内容は把握できてないが、処理の高速化を行ったらAPI呼び出しが増えてスコアが下がってしまい、revertした。
17:10 Nginxの設定をチューニングしたりした
nginxのworker_connections
を増やしてみたり、ロードバランシングをラウンドロビンとLeast Connectedで比較してみたりした。
スコアはあまり変わらなかった。
この辺で、運営側に障害が発生し、ベンチマークが実行できなくなった。
17:30 撤退準備してたら51865点
ベンチマークが実行できなくなってる間に、撤退準備をした。
行ったこととしては、nginx,mysqlのログ出力停止、goのpprofを削除したりした。
その間にベンチマークが復活し、実行すると51865
点。
pprofの削除が効いたっぽい?
18:30 dropProbabilityに願いをかける
ベンチマークの障害により延長された時間でdropProbabilityに願いをかけた。
0, 0.5, 0.9の値で検証してみたものの、デフォルトの0.9が最もスコアが高かったため、0.9を採用。
18:45 競技終了 43797点
51865
点を取った際と特に差分はないはずだが、何故か再現することができず43797
点でフィニッシュ。
結果
失格になりました。
DB等をローカルからアクセスできるようにするために、AWSのセキュリティーグループのインバウンドを追加した事が原因です。 レギュレーションをちゃんと読んでおらず、禁止されていることを認識せずに何も考えずに行いました、、、
振り返り
良かったこと
- チームメンバーの役割分担を予め決めておいたこと
- 計測ツールの導入、実行方法を予め確認できていたこと
- サーバーリソースの確認、ログの解析を行いながらチューニングが行えたこと
悪かったこと
- レギュレーション、アプリケーションマニュアルをちゃんと読んでいなかったこと
- INDEX追加等、とりあえずできることをすぐにやらなかったこと
- ミドルウェアのチューニングをなんとなくで進めてしまっていたこと
今年できなかったけど来年はやるとよさそうなこと
まとめ
チューニングを愚直にやることで、着実に点数が上がる問題だった。
結果として失格にはなってしまったが、ちゃんと計測し、チームで色々議論しながらパフォーマンスチューニングできて楽しかった。
来年も出場して本戦出場したい。
appendix
shutarouの参加ブログ chouxcreams.hatenablog.com
使用したGitHubレポジトリ github.com