公式の過去問レポジトリ isucon
過去問の実行環境を整えたいならここのvagrantとansibleを使うと便利 matsuu/vagrant-isucon
各種言語で実装されたウェブアプリケーションがMySQLをデータベースとしてシングルインスタンスで動いているので、そこに大量のリクエストが来たときに上手にさばけるように改造する。 ただし、レスポンスの内容は基本的に一字一句変更できない(例えばJavascriptファイルをminimizeして、たとえ表示内容が変わらなくてもNG)(クッキーの中身とかはOK)。
大会が始まったらまずやること
- ルールをよく読む
- 解法を探るためのヒントが落ちていることがある
- こういう場合はこういうレスポンスを返しても良い、というルールをうまく活用することでスコアが伸びることもある
- スコアの方式は毎年微妙に変わったりするので要注意
- 再起動試験などの手順はよく確認しないと最悪の場合失格になる
- 問題プログラムを共有する
- サーバー上に置いてあるプログラムファイルをローカルに持ってきて、Githubなどでチームメイトと共有する
- シングルファイルで構成されているので、エンドポイント毎にモジュール分割するとコンフリクトが少なくいいかも
- 使わない言語の実装は適宜取り除くといいかもしれないが、移植時のバグの可能性を考えてGo言語のものはリファレンスとして残しておいてもいいかも
- サーバーの構成を調べる
- CPUの数、メモリ、ディスク容量、使われているDBなど
- 年によってはサーバーごとにメモリやCPU数が違ったりするので注意
- ボトルネックを探る
- アクセスログや実装を見てあげることでどこがボトルネックかを分析することでどこに高速化できる余地があるかを探すのがまず第一
- 言語のプロファイラ、アクセスログをalpで解析する、pt-query-digestでSQLのスロークエリを探す、など
高速化するにはだいたい以下のようなことをしていくと良い。
- マルチインスタンス化
- そのままサーバーを複数立てるだけでは当然駄目なので工夫が必要。
- ちゃんとロードバランスする以外にもエンドポイント毎に役割を分ける、静的ファイル配信専用サーバーをつくる、など
- 場合によっては変にマルチインスタンス化するよりもシングルインスタンスの性能を詰めてしまったほうが速い、ということもある
- チューニング
- nginxやmysqldの設定をいじることでパフォーマンスが向上する
- どこが遅い原因かを考えながら設定する
- データベースとの通信の最適化
- N+1問題の解決、必要なカラムのみをとってくるようにする、など
- 不変なデータが置かれている場合もあるので、そういう場合はメモリに載せてしまう、プログラムに埋め込んでしまうなどの工夫をする
- テーブルに新たなGenerated columnを追加することでクエリが最適化できることも
- データベース以外にも外部APIの通信がある年もある。こちらも呼び出し回数の最小化、並列化などで最適化できる場合がある
- Redisなどの異なるデータベースを使えば高速化できる可能性がさらに広がる、が実装は複雑になる
- ユーザー認証
- bcryptによるhash化は遅いのでストレッチング回数を減らすこと、そもそもアルゴリズムを変えてしまうことにより高速化が可能
- この部分はISUCON9の予選で判定が紛糾したこともあるので、変更不可となるかもしれない
- 初期実装にバグがあることもある
- 特にGo言語以外だとバグっている可能性は高くなる。過信は禁物。
- 部分的に改良しある程度高速化が進むと顕在化する
この辺は個人の好みの問題もあるのでチーム内で要相談
短時間の開発で、変更が容易に反映されることを目指した方針
- ウェブアプリのデプロイはrsync等でコンパイル済みのものをアップロードする
- サーバーは貧弱でコンパイル時間が長くなることも
- Windows・Macユーザーの場合、ちゃんとサーバー上で実行できるLinux用バイナリが吐けるか要確認
- Gitレポジトリを経由する・CIを使うとかは時間が限られたコンテストの障害になるので避ける
- サーバー毎に変えたい設定は環境変数経由でコントロールする
- systemdの
EnvironmentFile
が例年だと設定されているので、それを使うと便利
- systemdの
/etc
以下の設定ファイルはetckeeperに任せ、リモートのGitレポジトリは経由させない- リモート経由だとパーミッション設定とかで面倒なことになる
- サーバーごとに微妙に設定を変えたい場合も困る
- サーバー上のDBの状態を確認したい時はSSHトンネリングを使うと便利
ssh -fNL <local port>:localhost:<DB port> <remote-addr>
- ベンチマークを走らせる前の処理・後の処理はシェルスクリプト化するなどして自動化するとよい
例年の傾向からおさえておきたい知識
大体、mysqlのN+1問題を潰すのが大きな仕事だったりするので、mysqlの文法はきちんと勉強しておくといい
チューニングはまずはスロークエリのしきい値を0にしてしまってからmysqldumpslowやpt-query-digestで解析してあげると、どのクエリが頻繁に呼ばれているか、どこを集中的にチューニングするべきかがわかる。 参考:スローログの集計に便利な「pt-query-digest」を使ってみよう
Rows sentがRow examineに対して大きすぎる場合、indexを貼り直すことを検討する。複合インデックスを用意しておいた場合でもFORCE INDEX
句を付けないとちゃんときかないこともあるのでEXPLAIN
句でどのインデックスが実際に使われているか確認する。
参考:オトコのソートテクニック2008
さらなるチューニング(クエリキャッシュのサイズなど)はMySQLTunerを使って何が悪いかを突き止めるといいかも? 参考:MySQLデータベースのパフォーマンスチューニング
このレポジトリの下に過去に自分が使ったansibleが置かれている。コンテスト開始直後にとりあえず走らせるというのができると気が楽なのでおすすめ。複数サーバーに対しても走らせられるし。 よく使うコマンドのインストールや簡易版のvimrc以外にやっていることとしては
alpというのがISUCONではよく使われる。
アクセスログを特定のフォーマットにすればnginx以外でも利用可能(ISUCON8で使われたH2Oとか)
- 公式ドキュメント
- nginxパフォーマンスチューニング〜静的コンテンツ配信編〜
- 静的ファイルをアップロード&ダウンロードを複数サーバーでやるには
try_files
が便利そう。参考: nginx serve from static if not found try reverse proxy
キャッシュ機能を持つリバースプロキシとして使える。nginxとウェブアプリの間に挟むことでレスポンスを手軽にキャッシュできるらしい。 変更がないデータ以外にも、更新にタイムラグが許されているエンドポイントに使うとかの技がある。
Key-Value型のインメモリデータベース。データベースの一部カラムをRedisで管理することで高速化できたりするはずだが、大抵変更量が多くて下手に手を出してもうまくいかないことが多いように思える(個人の感想)。 使うつもりなら事前練習をしっかり積んだほうがいい。
MySQL 8以降だとクエリキャッシュが効かない。かわりにProxySQLをかますなどでキャッシュできる。 が、本来の用途は冗長化とかにあるっぽいので、果たして有効な手段かわからない。