tl;dr;
タイトルが全て
- tl;dr;
- 今年の準備
- リポジトリ
- PRとDatadogを見ながら当日やったことを振り返り
- 当日10:00〜18:00のDatadog晒し
- 具体的にやったこと
- CloudFormationから起動した直後のインスタンスにベンチマーカーを実行(スコア:4148)
- いつものスニペットをコピーして参照実装をGoからRubyに変更(スコア: 3781)
- Sentryとddtrace(DatadogのAPM)を有効化(スコア:3497)
- rubocop-isucon導入(スコア:3389)
- GET /api/user/:username/icon : DBにある画像を逃がす(スコア:3827)
- fill_user_responseでDBの画像を使わないようにする(スコア:4518)
- 2台目のサーバをDB専用として投入(スコア:6893)
- MySQLのスローログを無効化(スコア:7028)
- index追加祭(スコア:7132)
- livecomment_modelがnilってNoMethodErrorになったので修正
- 3台目投入(スコア:7228)
- 17:21頃に再起動試験
- appを1台で動かす前提にする(スコア:6949)
- 最後にDatadogとか諸々無効化(スコア:10663)
- 最終スコア
- 来年に向けて
今年の準備
ISUCONの前の週に Google Cloud Next Tokyo ’23 に登壇した関係で今年の準備期間は実質1週間程度でした。
とりあえず勘を取り戻すために下記の素振りをしてました。
リポジトリ
競技用のリポジトリ
ISUCON用の汎用Itamaeレシピ集
PRとDatadogを見ながら当日やったことを振り返り
当日10:00〜18:00のDatadog晒し
sinatraのAPM
競技中はここを見るのが一番多かったです。
サーバ全台
puma
pumaのメモリ以外のメトリクスが後半しかないのは、DatadogのPuma Integrationの設定を入れたのが後半なため。
MySQL
具体的にやったこと
CloudFormationから起動した直後のインスタンスにベンチマーカーを実行(スコア:4148)
いつものスニペットをコピーして参照実装をGoからRubyに変更(スコア: 3781)
この時点でRuby 3.3.0-devにしてます。
Sentryとddtrace(DatadogのAPM)を有効化(スコア:3497)
ここでようやくスタート地点
rubocop-isucon導入(スコア:3389)
いつもならこの時点でN+1がどれくらいあるかindexを貼る箇所を見つけているのですが、今までとコードの書き方が微妙に違うせいでASTの構成が変わって Isucon/Mysql2部署のcop が全く動かなかったのがショックだった、、、
GET /api/user/:username/icon : DBにある画像を逃がす(スコア:3827)
Datadogを見たら GET /api/user/:username/icon
が明らかにボトルネックになってたのでこいつの改善に着手。
エンドポイントを選んでDurationの降順してボトルネックを発見。
小ネタですが https://github.com/sue445/isucon13-20231125/blob/main/ruby/config/ddtrace_init.rb#L55-L65 のようなモンキーパッチをあててSQLの生クエリ(プレースホルダの ?
が入ってないやつ)をDatadogに送信してexplain取りやすくしてます。(ユーザが入力したパスワードもDatadogに全部生で送信されるので本番アプリには絶対にいれたらダメなパッチ)
ソース見たらDBに画像が保存されてたのでnginxで返そうとしたのですがここで1時間以上手こずったと思います。
ChatGPTに聞きながらやってもいい感じのnginxの設定が書けずに最終的にローカルに保存した画像をsinatraで返すようにしました。
最終的にこんな感じでボトルネック改善したのを確認。
これでもまあ悪くないので1時間もかけずに早いところ諦めるべきだったね、、、(1人チームだとハマった時に無限に時間が溶ける)
fill_user_responseでDBの画像を使わないようにする(スコア:4518)
最後の方で気づいたけど画像を保存する前に計算したhashをDBに入れた方がよかった。
2台目のサーバをDB専用として投入(スコア:6893)
ここも微妙にハマったやつ。
デプロイ時に1台目のサーバのMySQLを止めて2台目のサーバのみMySQLを起動してるのに、デプロイ後になぜか1台目のサーバも止めたはずのMySQLが復活してむっちゃわけ分からずにハマってました。
最終的にはserviceファイルのAfterとRequiresが原因とわかって消したんですが、そうしたらPowerDNSが使ってるMySQLにも影響して復活させました
MySQLのスローログを無効化(スコア:7028)
どうせ見ないので無効化した
index追加祭(スコア:7132)
POST /api/initialize
でやってるTRUNCATE TABLEだとindexが消えなくて、かといってMySQLだと ADD INDEX IF NOT EXISTS
のような冪等性のあるDDLが分からなかったのでinitializeで実行されるsqlファイルに入れるのを諦めて手動で投入することにしました。(gitの履歴としては残したいのでこういうファイルだけcommit)
livecomment_modelがnilってNoMethodErrorになったので修正
index追加中にこういうエラーに遭遇したので修正。こういう時sentry使ってると検出できて便利っすね。( https://sentry.io のqueueが詰まってたせいか分からないけど最初の方は全然エラーが通知されなくてつらかったけど)
3台目投入(スコア:7228)
app専用として3台目を投入して負荷分散しようと思ったんですが、appがローカルのPowerDNSに依存しててそのPowerDNSを別サーバに動かすのが手こずりそうだったので実際の負荷分散は後回しにすることに。
17:21頃に再起動試験
appを1台で動かす前提にする(スコア:6949)
この時点で残り時間1時間きってたので危険な変更はしないようにしました
最後にDatadogとか諸々無効化(スコア:10663)
最終スコア
10,663 sue445.members.count==1 (再起動後スコア0)
ええ。マジ、、、
競技時間中に再起動試験して問題なかったのに、追試の再起動試験でスコア0になるとは思わなかった。
来年に向けて
- もっと素振りして練度を上げる
- https://github.com/sue445/rubocop-isucon の修正