ISUCON14に参加した

[ISUCON14](https://isucon.net/archives/58818382.html) にチーム名「さくたんさん」として参加して~~19252点で68位でした~~ 環境チェックNGで失格でした。

内容としてはやっちまった感満載なのですが、そんな中でも~~上位10%に入った~~のは昨年より地力がついたのだとポジティブに受け止めておきましょう（そう思わないと無力感に押し潰されそう）。

来年に向けて振り返りをしておきます。

## チーム
友人との2人チーム。

- 自分：インフラ周り+アプリケーション
- 友人：アプリケーション専念

という分担。普段の仕事がそんな感じなので。

## 昨年（ISUCON13）
ISUCONには昨年が初参加。
半年かけて友人と10回くらい集まって勉強して臨んだのですが、当日はDNSを何とかしようとしすぎて沼にはまり、282位という残念な結果に。

[チーム名](https://www.google.com/search?q=%E3%81%95%E3%81%8F%E3%81%9F%E3%82%93%E3%81%95%E3%82%93) からも分かる通り、私は重度の乃木坂46ファンでして、ライブ後に深夜バスで帰ってきて翌日ISUCON練習、のようなハードスケジュールで両立してました。

その割に何もできなかった無力感がでかすぎて、もう来年はいっかなとその時は思ってたのですが、時が経てばリベンジしたくなってくるもの。
今年も参加一択。

## 今年の準備
今年は10月に始動しました。
10月は乃木坂46を追いかけて、月の半分を全国各地のホテルで過ごす忙しい日々だったのですが、時間を見つけてやるしかないと。

そして気付きます。

「なんか俺めっちゃ強くなってる！！！」

どうやら普段の仕事をしているうちに、ISUCON力が上がっていたようです。
仕事は毎日のことなので成長実感はなかったのですが、技術スキルって上がってるんや。
これは嬉しかった。

考えてみると、昨年転職する前はインフラ専門でアプリケーションのコードを見ることがほぼなかったのですが、今の職場になってからアプリケーションのコードを見る機会ができたのが大きいのかなと。
（と言ってもバグ調査等で読むくらいで、書くことはほとんどなく相変わらずインフラ寄りなのですが、それでも0と1の違いは大きいっぽい。）

本番まで、3回ほど集まって過去問を解いたのと、あとは少しの個人練習。
ラスト1週間は毎日深夜に2時間くらい練習してて体調崩し掛けた。あぶねえ。

目標は30位以内。十分狙えそうな手応えがありました。

## 当日の流れ
### 10:10 初期スコア 965点
初期ベンチのあと、以下を終えて大体11時くらいに改善スタート。
- 構成管理する
- alp, pt-query-digest, pprofの仕込み
- マニュアル読み合わせ

### 11:24 インデックス張る(友人) 2698点
スロークエリを見たところ、以下の2つが主なボトルネックっぽいということで手分け。
- 基本的なインデックスが張れてない
- `/api/owner/chairs` のSELECTがくそ重い

前者のインデックス張りを友人にやってもらってすんなり。

### 13:37 total_distanceはchairカラムに持っておく(自分) 3309点

[PR#7](https://github.com/firemacho/isucon14/pull/7)

自分は`/api/owner/chairs`に取り掛かる。
`total_distance`と`total_distance_updated_at`はカラムに持っておいて必要に応じて更新しておくことで、SELECTを軽くする作戦。

他の選択肢としては、カラムではなくmapでキャッシュしておく作戦も考えられましたが、以下理由でカラム作戦を選びました。これは良い判断だったかなと。
- カラムに入れてもその処理はボトルネックにならなそう
  - UPDATEが重くなる懸念については、同時に実行しているchair_locationへのINSERTの負荷が大したことなさそうだったので問題ないと判断
- mapに持つよりもカラムに入れる方が早く実装できそう
- もしこの先カラムに入れる処理がボトルネックになってきたら、その時にキャッシュすればよし

これに2時間半もかかったのが反省。このくらいは1時間以内でやりたい。
手間取った理由は以下。
- 初期値の扱いを考える必要があった
- 初期データを計算して突っ込むSQLをGithub Copilotに書いてもらったら絶妙にミスってた

気付いたんですけど、現時点ではGithub Copilotに依存するのは良くない。依存するくらいなら全く使わない方が早いわ。
自分がしっかりロジックを理解した上で、文法がパッと出てこないからググると時間がかかる……くらいの時に代筆をお願いするくらいの使い方が良くて、まるっとお願いすると精度も悪いし会話するのにも結構時間がかかるのでダメだと思いました。

本番という焦りもあって、AIに頼ろうとしすぎてしまった。自分でしっかり考えるべし。

### 14:03 諸々の修正の追加(自分) 4829点
以下でスコアアップ
- InterpolateParams=true
- アプリのデバッグログを切る
- スロークエリを見て必要そうなインデックスの追加（日付descのorder by狙い）

### 14:39 nginxでepiresを設定(自分) 5130点
imageやassets系のファイルが結構多そうだったので、クライアントにキャッシュさせた方がいいだろうなということで。

ただ1行追加しただけなのに30分以上もかかっているのは、nginx内でのキャッシュ設定とか、最適を探って何回もベンチ回してたから。
でも数百点の上下は結構誤差っぽくて、あんま調整する意味なかった。そのことにしばらくしてから気付き、ああもうexpiresだけでいいや！と。時間浪費した。

### ～16:00 2人してSSEに沼る

ここまで友人には何をしてもらっていたかというと、通知のSSE実装へのトライでした。

通知エンドポイントへのアクセスが多くそれが負荷になっていそうだったので、マニュアルにも書かれているしSSEにした方がいいだろうという判断でした。
ただ難易度がかなり高かったようで、うまく動かないとのこと。
ここで諦める選択肢はあったのですが、スコアが他チームよりも伸び悩んでいてここを解決しないことにはスコアは伸びないんだろうと思い込み、2人して沼に突入してしまいました。
判断ミス。
実際はそんなことなかった。

さらにそのもっと前の話をすると、友人はSSEでの定期通知の実装は早いうちにできていたのです。実はそれで良かった。
ただ、状態遷移きっかけの通知にしないと意味ないよねと考えてしまい、沼に突入。
自分も話は聞いていたのですが、自分の実装に脳みそを使っていて、ちゃんと落ち着いて考えることなくその判断を勧めてしまいました。
反省。

### 16:24 accesstokenに対してchairをキャッシュ(自分) 5083点
16:00過ぎに自分は身を引き、できることをしていこうと。

通知周り以外だと、スロークエリを見るに`SELECT * FROM chairs WHERE access_token = ?`に負荷がかかっていたのでキャッシュしてみました（SSEの話と並行で準備してた）。
しかしスコアは伸びず。
しかもこのあと400系エラーが発生する原因になった（たぶん）ので結局戻した。access_tokenは変わることあるんかな。ちゃんとコードを見る余裕もなかった。

結果的に言えば、マッチングのロジックの改善がボトルネックになっていて、キャッシュうんぬんよりそこを解決しないとスコアは上がらない問題だったようです。
その点については、最初のうちにコメントを見つけていて改善した方がいいんだろうとは思っていたのですが、もうこの時にはちゃんと考える余裕がなかった。
マニュアルをちゃんと読んでアプリケーションの仕様を考えたり、ベンチマーカーのログをしっかり見たりすることの重要性。
焦らず、落ち着いて考えないと。

### 17:11 DBを分離+通知リトライ間隔を300msに(自分)  13804点
[PR#15](https://github.com/firemacho/isucon14/pull/15)

やばいもうこんな時間だということで、慌ててDBを別サーバに分離。
分離したらベンチマーカーでイスの状態が正しく取得できていないエラーが発生するようになったので、リトライ間隔30msが短すぎるのが原因だろうと思い300msにしたら、ベンチは通ってスコアもぐんぐん上がった。

なんだよこんなことでスコア上がるのか！
既に敗戦ムードだったけど、急に希望が見えてきた。

マニュアル読んでみたら3sの遅延まで許容されるとのことだったので、30msなんて短い間隔でリトライさせる必要がなかったんだ。
で、これはいいぞとなって、このあと間隔をいろいろ調整してみたのですがスコアは上がらず、結局300msくらいが丁度いいらしいぞと。

また、期せずして、この時点でマッチングの処理がAPPサーバとDBサーバの2台で実行されるようになったので、それもスコア上昇効果があったのではと思います。

### 17:52 残りの1台をマッチング処理専用機&intervalを0.01に(自分) 16848点

1台空いているのはもったいないぞということで、APPサーバとDBサーバの`isuride-matcher`は停止&disableし、残りの1台で動かすように。

マッチング処理しか動かないなら、インターバルの間隔をもっと短くできるだろ！ということで、あまり調整の暇もなく0.01に。
環境変数を修正したあとにデーモンをrestartする必要があることにしばらく気付かず苦闘。
なんとか滑り込みセーフ。

後から考えてみると、マッチング処理を動かしてるサーバの負荷は大したことなかったですが、あまり頻度を短くし過ぎてもDBアクセスが増えてDBの負荷が上がってしまうので、短くし過ぎるのもダメそうです。
結果的には丁度いいくらいだったのでしょうか。

自分としては、残り時間がなく焦っている中でも、インフラ構成変更周りを滑り込みで行えたのは良かった点だなと思います。
焦っていても、慣れているインフラ周りならなんとかできる。
そう思うと、アプリケーション実装で本番の焦りに打ち勝つには、焦っていてもできるだけのスキルと慣れを身に着けることなのかもしれません。

### 17:58 スロークエリログ&pprof&nginxアクセスログを切る(自分) 19252点

ベンチを回す時間が一回しかなく、ミスっていたらおしまいという賭けでしたが、なんとか上手く行って19252点でフィニッシュ。

vscodeの接続を外すことは忘れていたし、定番の SetMaxOpenConns/SetMaxIdleConns の調整すらできなかったのは悔しいところですが怒涛の最後の1時間のスコアアップでした。
各種パラメータももう少し調整したかった。。。

### まとめ
ラスト1時間での伸び率（約3.5倍）ならTOP3には入っていそうでしたw

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3891777/b674bfa0-c303-4a85-2b59-83e66fde5d9c.png)

## 反省
- 実装できる確証がないものは手を出さない
  - 昨年はDNS水責め攻撃対策、今年はSSE配信、と2年連続で大やらかしした
  - 自分らが難しいものは他の人も難しい。目標が30位以内ならそこまで取り組む必要はない
  - それ以外でスコアが上がらなくなった時だけ着手しよう
  - なるべく、どでかい変更はいれずに、動く状態を保ったまま細かく改善を積み重ねるのが理想
- とは言え、SSEを諦めていたとして、マッチングのアルゴリズムを改善する必要性に気付けていたかというと微妙
  - スロークエリログの結果改善に最初に手をつけるのは仕方ないとして、スコアが上がらなかった時には気付きたい
  - 解析結果を眺めるだけではなく、アプリケーションの仕様自体に立ち戻って考えることの重要性
  - ベンチマーカーのログもちゃんと読む
  - コード内のコメントはヒントなので重視すべき
- 落ち着いて判断することの重要性
  - 開始前から「判断ミスったら平気で1,2時間溶かすから、実装取り掛かる前にちゃんと考えて判断しよう」と言っていたのに守れなかった
  - 時間を溶かしてしまった焦りから、さらに判断を間違えていく負の連鎖
  - 例えば1時間おきにアラームを設定して、5分ほど強制的に手を止めて方針を再考する時間を設けるのもありかも
- AIに頼り過ぎない
  - 思考停止の原因となりがち
  - ロジックは自分で考える
  - チャットは地味に時間を浪費してる
- 終盤の定型作業はもっと余裕をもってやる
  - APPとDBの分離はほぼ間違いなくやるんだからもっと早めに
  - パラメータがある場合はその調整にも時間をかけたい
  - 最後のスロークエリログ等をオフするPRなどは最初のうちに作っておく。時間がないと焦る。
  - __最後のチェックをする余裕がなくて失格した。__（公開鍵追加してたのが原因っぽい）

今日は乃木坂46の動画でも見て寝ます。中村麗乃ちゃん可愛い。