🪑

ISUCON11予選に参加しました(敗退)

2021/08/28に公開

ISUCON11予選に「JSON・ボーヒーズ」というチームで参加し、最終スコア45,008点(88位)で敗退しました。

チームについて

同僚2人と3人チームで参加しました。
役割としては、私が最初の環境整備とインフラ担当、他の二人がアプリ担当という分担でした。
全員今回が初参加でした。

やったこと

参考実装はGo言語を選択(初期スコア1,660点)

  • isu テーブルに3つINDEXを追加(1,421点)
    • (jia_user_id, id), (character), (jia_user_id, jia_isu_uuid)
  • isu_condition テーブルにINDEXを追加(16,298点)
    • (jia_isu_uuid, timestamp)
  • DBサーバを分離し、2台構成に(22,402点)
  • slice の生成時に length と capacity を指定する(24,220点)
    • メモリの再割り当て処理を減らす
  • GET /api/trend のクエリに LIMIT 1 をつける(30,448点)
    • 結局1つしか使っていなかったので
  • slice の生成時に length と capacity を指定する・その2(31,436点)
  • sqlx の Open 時に interpolateParams=true を指定して、プレースホルダ置換を有効に(37,395点)
    • pt-query-digest で見たときに、PREPARE が上位に来ていたので
  • assets を nginx から返すように(38,201点)
  • POST /api/condition/:jia_isu_uuid の INSERT を Bluk Insert に(44,701点)
  • ログをストップ(46,220点)

やりたかったけどできなかったこと

  • isu_condition テーブルの condition カラムを正規化して、文字列比較での判定をやめる
    • generated columns で実現しようとしたが、そのためには 1_InitData.sql での INSERT 時にVALUESのカラム名を明示的に指定する必要がありそうだった
    • このファイルを直接エディタで書き換えたところ ISU のアイコン画像が壊れるという現象が起きたので泣く泣く諦めた
    • 終わってから考えると、このファイルをいじらず新しくsqlファイルを用意して ALTER TABLE で generated columns を追加すればよかった……
  • GET /api/isuGET /api/trend の N+1 問題解消
    • 一度で結果を取ってくるクエリを書いたものの、そのクエリが遅すぎて却ってスコアが下がってしまったので無念の revert
    • 終了直前にチームメイトが別の方法でのクエリを書き上げたが、Failed したので修正が間に合わず
    • 他のチームの参加記を読むと GET /api/trend はキャッシュをするのが正解だった模様
  • assets をキャッシュする
    • nginx から返すようにした際、expires ディレクティブを付けてキャッシュするようにしたつもりが、ブラウザから見たらキャッシュされていなさそうだった
    • キャッシュ周りの知識が付け焼き刃すぎて、キャッシュされているかどうかの判断ができなかった
  • ISU のアイコン画像をキャッシュする
    • 認証をどうしたら良いのかがわからずキャッシュできなかった
    • 感想戦にて、 Go で認証したあと X-Accel-Redirect を使って nginx に内部リダイレクトすれば良いということを知った

事前準備

デプロイやアクセスログ解析まわりの整備

必要なツールのインストールや、共通で必要になる ssh, git の設定を1コマンドで実行できるように、ansible の playbook を書いていました。

また、デプロイ用のスクリプトとして、コードの反映、ビルド、ログのローテート、サービスの再起動を行うシェルスクリプトを用意していました。
これのおかげでサイクルが早めに回せていたのは良かったのかなと思います。
ただし、複数台構成に対応するため設定が複雑になってしまい、最初動かすのに手間取ってしまうのは改良の余地ありです。

そして、ベンチ実行後に alp や pt-query-digest で解析を行って、その結果を GitHub の Issue にコメントとして投稿するシェルスクリプトも作成しました。
その時点のコミットハッシュも Issue に埋め込んでおくことで、どのコミットでその結果になったのかがわかるようになっています。
また、1つのIssueに投稿していくため、タイムラインのように以前の結果と比較することもできて、個人的にはお気に入りです。

一応ソースコードはこちらで公開しています(READMEも何も無いですが)
https://github.com/JSON-Voorhees/isucon-utils

素振り

本番の2週間前にISUCON9予選、1週間前にISUCON7予選を使って、それぞれ8時間の通し練習を行いました。

全員が今回初参加だったので、この練習を行ったことで、ISUCON の流れのようなものを掴むことができました。
また、前述のデプロイスクリプト等を実際に使ってみて少しずつ改良することもできました。

この練習を行う前は、ローカルでの開発環境を作るかどうかを決めあぐねていたのですが、この2回の練習でローカル開発環境を作るのはコストが高すぎるということがわかったので、本番では作らない方針に決めました。

パフォーマンスチューニングに関してはもちろん、それ以外の部分でも学びが多かったので、当然ですが事前の練習はやってよかったと思います。

当日の動きについての反省点

ドメイン知識の不足

最初に全員でマニュアルを読んだのですが、グラフ周りの得点の仕様をいまいち理解しないまま流してしまいました。

また、POST /api/condition/:jia_isu_uuiddropProbability を一度も触らずに終えてしまい、これを変えたときにどんな挙動になるのかというのを把握しないままでした。

このあたりのドメイン知識が不足していたのは良くなかったポイントだと思います。

デプロイ方針

condition カラムの正規化や N+1 問題の解消については、途中まで取り組んで revert してしまったのですが、諦めるのが少し早過ぎたかなと思います。

この原因として、 main ブランチのみデプロイするという方針のせいで、1つ詰まってしまうと他の改善が何も試せなくなってしまい、焦ってしまったということがあると思います。

ですので、 main ブランチにマージしてからデプロイするのではなく、ブランチ指定でデプロイして、ベンチが通ってから main ブランチにマージすべきでした。

役割分担

また、「やったこと」を見ると分かる通り、インフラ的な改善がほとんどできていません。
インフラ担当といいつつ、アプリの改修も行ってしまっていたので役割分担があまりできていなかったと思います。

インフラ的にも色々できることがあったはず(3台全部使うとか、キャッシュ周りとか、DBのチューニングとか)なので、アプリの改修はチームメイトを信じて任せるべきでした。

感想

初参加にしては健闘できたかなという感じではありますが、私はあまり何もできていなかったので悔しさのほうが大きいです。
来年までに付け焼き刃ではない知識を身に着けて、リベンジしたいです。

Discussion