毎年恒例の ISUCON に参加してきた。メンバーは毎年恒例 @acomagu & @natumn 今回はノベルティが欲しかったのもあり個人スポンサーになってみた。
結果としては最高スコアで 56180 点を出すことができたが、再起動試験に失敗し fail でした…。
事前にやったこと
今年はあらゆるものを自動化しようとしていて、具体的には以下のことをしてみた。
- Ansible: もともとだいぶ前から Ansible で初期セットアップを自動化していたけど、今回はさらにいろんな設定を追加してみた
- ツール群のインストール
- Netdata のセットアップ
- まったく使わなかったが…
- Cloud Profiler/Cloud Trace で使うクレデンシャルまわりの設定
- Slack 通知用の設定
- MySQL のユーザ作成・権限追加
- nginx のログフォーマットを LTSV に変更
- コード自動修正ツール: ソースコードに対してこのツールを実行すると以下の設定や実装を自動的に追加・修正してくれる
- 特定の関数やメソッドを context に対応しているものへ置き換える
- request context を各関数・メソッドへ渡すようにする
- サーバ起動時に Cloud Profiler/Cloud Trace 関連の設定、エージェントをスタートさせる
- ベンチ実行後に自動的に slow query や alp の解析結果を Slack に通知させる
どれもやってよかったけど、特に Cloud Profiler/Cloud Trace や Slack 通知周りが便利で競技中はほとんど手動で計測を行うことはなかった。ただ、後述するように準備の時間が十分にとれていなくて競技時間中にツールを修正したりしていたのが痛かった…。
当日やったこと
当日は @ktr の自宅に集まり、猫に見守られながら参加していた。
競技開始直後
- 各メンバーが ssh してサーバに入れるか確認
- サーバで起動しているアプリケーションの設定確認
- Ansible 実行
- コード自動修正ツールの実行、メトリクスやトレースが取れているかの確認
- ソースコードのバックアップ
- 当日マニュアルおよびアプリケーションマニュアルを読む
このあたりを分担してやっていった。自分は Ansible 担当でほとんど自動化されていたのでその間にマニュアルを読んでいた。ただ、その後に実施したメトリクスやトレースの設定が一部うまく動いておらず修正にわりと時間を費やしてしまった。
昼ごろまで
- アプリケーションのアクセスログを切る
- アプリケーションのログレベルを上げる
- MySQL の
interpolateParams=true
を設定 - MySQL を2つめのインスタンスへ分離する
- users テーブルに
icon_hash
を保持する - ボトルネックとなっているクエリに対してインデックスを貼る
今までは後半になってから複数台構成へ移っていたが、自分たちはインフラに対して比較的不慣れだったのでほぼほぼ初手で複数台構成へ変えることにした。とりあえずアプリと DB は間違いなく分けるだろう、ということで 2 台構成としており、今回はほとんどハマらず移行することができた。
あとは毎回やっている設定を行ったり、明らかなボトルネックを潰したりといった感じで午前中は終了。だいたい 6600 点くらいだった。
午後
- 一通りコードに目を通して改善できそうな箇所にコメントをつける
- Redis で user、livestream、ngword をキャッシュする
- トランザクションが不要な箇所の修正
- NG ワードチェックをクエリではなくアプリケーションだけで行う
- タグを DB ではなくアプリ内のメモリに持つ
- アイコンを nginx から配信する
- PowerDNS をやめてオレオレ DNS に切り替える
とにかく N+1 が多かったのでそのあたりを順番にキャッシュしていった。また、すべての DB アクセスでトランザクションが貼られていたのでコードリーディングしつつ本当に必要なところだけに絞っていった。
アイコンについては If-None-Match
の仕様があることが分かっていたのでそちらの仕様を利用するか、nginx から静的に配信するかで迷ったけど最終的に nginx からの配信とした。ただ、nginx の設定にドハマリし、1 時間くらい溶けたのでこの方針が良かったのかはわからない…。
また、このあたりの修正が終わったあたりで水責め攻撃による PowerDNS の負荷がボトルネックになってきていたので改善に着手し始めた。DNS の運用経験も PowerDNS の知識もまったくなかったので、チームで話した結果、Go で簡易的な DNS サーバを書くのが一番効率的かつ不確実性が低そうという結論になった。この方針はかなりうまくいき、30~40 分くらいで完全に置き換えることができた。
ここまでで 24000 点くらい。
ラスト 45 分
- MySQL の binlog を止める
- nginx のログを止める
- アプリケーションのログを止める
- メトリクスやトレースを取るのをやめる
- 残っていたスロークエリ用にインデックスを貼る
基本的に最終ベンチに邪魔なものはすべて止めていった。早めに終わったのでアプリケーションコードに変更を加えないで済むスロークエリの改善を行っていた。支配的なボトルネックではないと思いこんでいたので、多少スコアが伸びればいいなーくらいの気持ちだったが、なんとこれがめちゃめちゃ効いてスコアが倍くらいになったw
一番最後のベンチで最高点を出して競技終了。
終わりに
最終的に上位 30 位に入れるスコアだったのに fail してしまったのはとても残念 😇 時間がギリギリになっていたのと、おそらく大丈夫だろうという憶測で再起動チェックをしなかったのは最大の反省点かなぁと思う。
他にもツールがうまく動かなかったことや nginx の設定でハマったことで無駄に時間をかけてしまっていたところや、結局 3 台目のインスタンスを使わなかったところも反省点。このあたりは次回までにちゃんと対策したり・戦略を考えておきたい。
とはいえ今回の ISUCON もめちゃめちゃ楽しかった!競技が終わってからもしばらく ISUCON のことを考えていたくらいなので来年こそは良い成績を残したいなー。毎年一緒に出てくれる友人たちにも感謝 🙏
運営に関わった皆様ありがとうございました。
追記
インスタンスを構築していた友人によると fail の直接的な原因は自作 DNS サーバを systemd に登録していたものの、スタートアップ時に自動起動する設定になっていなかったとのこと。こういう凡ミスを避けるためにも再起動試験は必須ですね 😇