年も越そうとしているタイミングで今更感がありますが、通算4回目の参加となるISUCON10予選の参加記録をアップしたいと思います。(公開するのは初めて)
リポジトリはこちら
事前準備
前提事項として、これまでのISUCONでは業務の都合上PHPを選択していましたが、今回は転職した関係もありRubyを選択しました。
事前準備として、過去4回分の予選の問題を実際に解き、開催当時のランキング上位5位以内のスコアが出るところまでチューニングを試しました。
- ISUCON9予選
- ISUCON8予選
- ISUCON7予選
- ISUCON6予選
また単純にチューニングするだけでなく、デプロイやサービス再起動等の細かい作業に関して如何に楽にできるかも模索しました。
これらの作業用に自作シェルスクリプトを用意しても良かったのですが、色々探した結果、「Ansistrano」という素敵なOSSに落ち着きました。名称の由来はAnsible+Capistranoで、由来の通り、Capistranoの思想をベースとしてAnsibleのようにYAMLで書けます。
事前準備の環境は、手元にGCPの環境があったのでGCP上にTerraformで構築しました。事前準備の副産物として環境構築できるtfファイルが得られたのは良かったです。(tfファイルは徐々に公開していきたい)
過去に決勝で出たもの(複数台構成等)が次回以降の予選に出てきたことがあるため、ISUCON9の決勝の問題もやろうとしましたが、公開されている情報が予選よりも少なく環境構築するところで力尽きました。。。
また、今回はローカルにDockerで環境を再現し、ローカルでデバッグできるようにすることで、動作確認の手間を減らすようにしました。そのため事前にローカル環境のベースとなるものを作成しておきました。(コミット)
当日
ここからは当日にやったことをコミットログの流れに沿って、紹介します。(ここからは文章が常体になります)
開始直後、まずは参考実装や各種ミドルウェアの設定ファイルをGitHubにアップ。(コミット)
Redisを使えるようにredis.confを用意。(コミット)
以下の定型作業を実施。(コミット)
- アプリケーションサーバをunicornからpumaに変更。
- MySQL&nginxにお決まりのパラメータを注入。
- お決まりのGemを追加。
画像をローカルに置くのが手間だったので、ローカル環境の画像は提供されたサーバのものを参照するように設定。(コミット)
スロークエリログを参考にインデックスを追加。ソースコードを一読し、怪しい箇所にコメントを記載。「/api/estate/nazotte」のN+1問題にこの段階で気付けていたが・・・(コミット)
MySQL、Redisを3台目のサーバに分割。(コミット、コミット)
MySQLを3台目のサーバに分割したので、MySQLへのデータインポートしているinitializeは3台目のサーバで実行するように変更。(コミット)
pumaのworker数をチューニング。(コミット)
全てのサーバをアプリケーションサーバに変更。過去はこの構成でほぼハイスコアが出るのでこの構成ありきの頭になっており、この構成以外に頭を切り替えるのに時間がかかってしまった・・・(コミット)
mysqltunerの結果等を踏まえてMySQLのチューニング。(コミット)
さらにインデックスを追加。(コミット)
今回のアプリケーションでは文字列の部分一致検索があったため、業務での経験を踏まえて全文検索を試してみる。ここから全文検索の沼にハマってしまい、全文検索を設定したり、戻したりを繰り返し、かなりの時間を消費してしまう・・・。後々考えてると全文検索の部分が致命的なボトルネックではなかったので、もっと違う箇所に時間を割くべきだった・・・。コミットログには現れていませんが、全文検索のためにElasticsearchの導入も試した。(コミット、コミット、コミット)
当日配られたマニュアルを読むと、ボットの話が記載されていたので、とりあえずnginxで対処するように設定。(コミット、コミット)
MySQLの負荷が高かったので、3台目をMySQL専用に。(コミット)
popularityに対して降順インデックスを使いたかったので、MySQL8にアップグレード。過去問をMySQL8で動かす練習をしていたので、設定ファイルのMySQL8化はスムーズにできた!しかし、後から振り返るとMySQL5.7のままで良かった。ミドルウェアは常に最新であるべきという先入観が災いした・・・。(コミット、コミット)
ちょっとだけインデックスを追加。(コミット)
ここでMySQLを複数台構成に。通常のWEBアプリケーションであればMySQLのレプリケーションの設定をすればよいが、ISUCONのようにコンマ何秒でサーバ間でのデータの同期が取れている状態にするという要件は、MySQLのレプリケーションでは難しそう(検証未実施)であるため、自前でレプリケーションもどきを実装。マスタのMySQLを更新したら同期的にレプリカのMySQLも更新するようにした。(コミット、コミット)
残り時間が少なくなったので、少しでも余計な負荷を排除するためログを停止。(コミット)
MySQLを複数台構成にしたので、3台目のアプリケーションサーバを復活。(コミット)
最後の最後でテーブル数が少なく、テーブル同士の結合が無いので、テーブル毎にサーバを分ければ良かったことに気付く。しかし、時間がほとんど無いので全面的な見直しまでは対応できず・・・。(コミット、コミット)
そして、予選終了・・・。
最終スコアは「1,264」で予選突破ラインには遠く及ばず・・・。
振り返り
ここまでの内容を振り返って、良かった点、悪かった点を整理します。
良かった点
- 環境構築する過程でTerraformのレベルがアップした。
- デプロイ周りでAnsistranoという新しいツールに出会えた。
- Dockerで環境再現するノウハウが得られた。
- かなりの量の過去の予選の問題に触れることができた。(ISUCON5以前だと環境を再現するのがかなり難しそう)
- ベンチマーカーの実装を少し理解できた。(ISUCON9予選のベンチマーカーを修正して元の仕様以上の負荷をかけたりした)
悪かった点
- 過去自分が苦労した部分(全文検索)に無駄に拘ってしまい、本来対処すべきボトルネックに十分な時間が取れなかった。
- 過去問にオーバーフィットしてしまい、MySQLの負荷が支配的になるという過去の問題には無かったパターンの対策が不十分だった。(今回の問題の構成は一般的にはよくあるので事前に対策はできたはず)
- ISUCONではミドルウェアは常に最新にすべきという考えが根底にあり、バージョンの選定についてもう少し配慮すべきだった。
- GISデータについて勉強不足だった。
今回の結果を踏まえて、さらに修行を重ねて、ISUCON11(開催されれば)では念願の予選通過を目指したいです!