ISUCONの予選落ちする戦い方を公開します

※少々過激なタイトルだったので修正。

出場した結果、初期のスコアから毛が生えた程度の点数で無事予選落ちしました。

まずは運営の皆様・・・すばらしい企画をありがとうございました。スコアは惨憺たる有様でしたが、おかげでとても緊張感のある、充実した１日になりました。

さて、出落ち感が半端ないエントリなんですけど、気持ちがノッているうちに傷心を推して感想を書こうと思います。

当日まで／当日の過ごし方と、できたこと・できなかったこと、それを踏まえてどうするのがよかったかを述べようと思います。

# はじめに
[sayayooshi_tada](https://sadayoshi-tada.hatenablog.com/about)さんと2名体制で出場しました。

彼の振り返りエントリ[ISUCON8予選2日目に『チームますらぼ』で参加してきた！](https://sadayoshi-tada.hatenablog.com/entry/20180917/1537112623)がかなりシンプルで網羅的に書かれていますので、この記事では私の立場で思ったことをピックアップして書こうと思います。

# 役割

アプリ寄りの担当として参戦しました。実際に手を出した領域はミドル付近の改善が多かったです。なお、趣味レベルのPythonをたまに書く程度なので、プロダクションレディーなWebアプリまでの開発経験はないです。

知識レベルは、Webに関連する基礎教養はざっくり持ってる、といったくらいのレベル感です（ISUCON7上位陣の多くが共通して手を出してた改善領域の内容と背景を、記事を読んでなんとなく理解できる、といった程度で捉えていただければ）。

# 当日までの過ごし方

1ヶ月前くらいに、過去問(ISUCON7)を解いていました。9月に入ってからはなんやかんやでほぼ時間を取れなかったので、８月中に確保した３週間くらいが実質の練習期間となります。工数で言うと５人日くらいは使ってた気がします。

ISUON7では上位陣エントリの戦略なども見つつでしたが、ある程度はノーヒントでもそれなりに妥当なアタリの付け方ができていたと思います。チャンピオンデータのスコアがシングルインスタンスで18万くらいだったので、時間さえあればそこそこ戦えるのでは、と思っていました[^1]。今思えば、「時間さえあれば」の部分が本番における大きなネックであったと思います。。。

[^1]: 過去問では、DBにいる画像バイナリを外出ししてキャッシングを有効にするとか、非効率なクエリをJOINで一本化して結合キーにインデクシングしたりとかしました。

本番直前では、計測向けの設定作業（ミドルウェア設定や解析ツールのインストール手順、デプロイ手順あたり）を雑にまとめてプライベートレポジトリにpushして備えました。

# 当日の過ごし方

前半戦のほとんどは基礎的な計測環境を整えるタスクで終わってしまいました。

情けない話ですが、Webサーバーにnginxでもapahceでもなくh2oを使っているところでドハマりしました。正直、まったく想定していなかったです。計測ツールに[alp](https://tkuchiki.hatenablog.com/entry/2015/09/25/130900)を前提としていたのですが、ltsv形式にログを変えてもalpが使えない。結局簡単な自前の集計スクリプトを書いてごまかしたのですが、満足のいく情報量が取れるものではなかったので、最後までここが（情報収集の面でも浪費した時間の面でも）響いたように思います。

後半戦ではまずAP/DBの役割を分離しました。過去問の経験から、DBの初期化処理がベンチマークごとに走っている仕様は確認できていましたので、出場者の多くの方が詰まった「鈴木さんに怒られる」事案によるロスは最小限であったと思います。[^2]
M/Wの設定を小手先で変えてみたり、ちょっとしたクエリの改善などをしました。

[^2]: 「鈴木さんに怒られる」事案について。ベンチマーク開始時点でベンチマーカーが想定した通りの初期データが入っていなかったために発生するエラーです。ベンチマーク実行の過程でデータの変更が起きるので、ベンチマーカーは実行ごとに（ `/initialize` を叩くことで）データを初期状態に書き戻す挙動を行います。AP/DBを分けた際に、この初期化処理を放置すると発生します。DBが分離するため、接続情報の設定を変える必要があります。

レポート生成処理 `/get_admin_event_sales` が、csvを都度計算して返すといういかにも重たそうな処理であったので、その辺をなんとかできないかと思案していました。ここについては、がっつりクエリを書き換えることは時間内で不可能と判断しました。そのため、ベンチマーク成否に影響しないorder byを抜いたりと、簡単にできることをやりました（結局、ベンチマーク上の有意性はなかったのですが）。

最後の2時間では、レポート生成処理のクエリ改善を諦め、ベンチマークが出力した `login` のタイムアウト問題を考察していました。結果、（経緯は忘れてしまったのですが、、） `get_event` のクエリが重たいことに気づき、なんとかできないかと考えたのですが、こちらについては着手途中でリミットが来てしまいました。

再起動テストは割と早めに行っていた＆その後大した改修ができなかったので、締め切り直前の30分でパスしました。

# 反省点
ここまでで既に懺悔することだらけなのですが。割と改善ポイントはシンプルだと思います。

結局のところ（時間内で） **できないことはしない** 、これに尽きるのかなと[^3]。

[^3]: まぁ、これができたところで「簡単にできる別の策」が出せるかどうかは別の話ですが...

具体的な心当たりは2つあって、1つは「h2oでハマって結局alpが使えなかったこと」、もう1つは「実装に時間のかかること（＝クエリの大幅な書き換え実装）を早めに切れなかったこと」です。

前者は、とにかく **「手に馴染むツールを使う」** ことで解消できます。過去問ではnginxとalpの組み合わせにより比較的高い精度でボトルネックを特定できていました。h2oのお作法と照らしながらalpが使えるようにフォーマットを合わせるのはそれなりに面倒なタスクでしたので、それならば最初からAP/DBを分離する段階で（練習の成果が出せる）nginxに切り替える判断をすべきだったと思います。

後者は、過去問練習時においてクエリ最適化に費やした時間を鑑みれば諦めがついたと思います。正確な工数まではつけていませんでしたが、実感的には過去問にかけた時間の大半がこのクエリ最適化でした。現在の私の実力に照らせば、本番中の僅かな時間でこれに着手するという判断はほぼ自殺行為であったかなと、今では評価しています。

とはいえ、本番中は焦りや経験値不足も相まって他の選択肢がろくに浮かばない状況でした。前回の出題で複数台構成が出ていたことは把握していましたので、そのレギュレーションに応じた改善策を持ってくるのが得策だったと思います。過去問ではシングルインスタンスしか練習をしていなかったので、せめて情報収集くらいは万全にしておくことで土壇場の策もひねり出す余地があったかもしれません。

# 時間があったらやりたかった

3台目の有効活動と、クエリ最適化です。レポート生成と `get_event` の抜本改善、どちらかはやりたかった。

レポート生成部分のクエリですが、`FOR UPDATE` という、DBにロックをかける句が含まれていました。これが3テーブルに跨ってJOINするクエリであり、他のリクエストに波及しまくっていることは容易に想像できました。

終了間際に賢者モード入りしたことで気づいたことですが、とりあえずロックの影響を回避するために `FOR UPDATE` を外してみることはできたし、なんならこの処理だけを3台目のサーバーに委譲するという手も有力だったかなと思ってます。

# 次回出場したら何するか

過去問は可能な限り2回以上反復して、実装を組み立てる部分に慣れるようにします。

カンニングを許容した上で、実際の時間を想定したリハーサルを取り入れます。時間との兼ね合いで戦う、ということが今回は抜けており、結果としてあまり時間を有効活用できなかったと思います。

# おわりに

私のようなバックグラウンドを持つ人が本記事の準備レベルで挑むとこうなる、という話でした。たぶん、アプリ実装経験者でもクエリ最適化は重たいタスクに入るはずなので、それは最初から切る想定でも良かったかもしれません（上位陣でも割と重たいタスクとして認識されている感じがありますし）。

複数台構成を活かした負荷分散など、よりハードルの低い施策の引き出しを持つことが重要だと思います。そのためには、やはり過去問を当時のレギュレーションでこなすというのが一番良いような気がします。

悔しさも含めて、持ち帰るものが非常に多いコンテストですので、まだ出ていない方は尻込みせず是非来年出場してみてください。