ISUCON14 振り返り

2024-12-08 に開催された ISUCON14 に参加した記録です。
次回以降に向けてやったことなど振り返っていきます。

去年の記録↓

notfounds.hatenablog.com

tl;dr

特に何もできず惨敗でした

ベスト: 8533 最終: 2,705

メンバー

去年と同じ。ただしメンバー2は当日事情があり欠席となった。

役割としては、自分がインフラ周りをメインで見つつ飛び道具的な改善に取り組み、もう一人のメンバーにアプリケーション周りの改善を進めてもらう。

タイムライン

初動(~10:20)

  • メンバー1
    • ブラウザから動作確認できるように設定
    • ER 図の作成
  • @NotFounds
    • SSH でサーバーに入り Ruby 実装に変え初回ベンチ
      • → 動かないので焦る
      • → リーダーボード見るとみんな0点なので安心する
    • Ansible で tool のインストール
    • GitHub にバックアップ
    • 初回ベンチ通る 1010 点
    • alp/slp のために Nginx/MySQL の設定を流す。ついでに NewRelic も入れた(Infrastructure Logs のみ利用)→ 699

全員でマニュアル読み(~11:00)

  • マニュアルを読み用語・概念確認
    • 今年は SSE かあ。そこまで手が付けられるといいねーとかいう話をする
    • Idempotent-Key とかは後でやれそうだねーみたいな感じ
    • 点数計算
      • 評価とか直接関係ないのか?
        • → その後の利用に影響がある
      • オーナーの売り上げとか直接関係ないのか
        • → 椅子の追加とかに影響がある

ぱっとできる改善フェーズ(11:00~13:00)

  • メンバー1
    • Index 貼り
      • alp/slp が改善するも MySQL の負荷は高い
  • @NotFounds
    • 少しでも DB 負荷を下げたい & 呼び出し回数が多かったので AccessToken から User/Chair を引く処理をインメモリキャッシュ → 効かない
    • ActionLog
    • MySQL の負荷が高いので3台目に移す → 3703 → なんか不安定になって revert

一進一退フェーズ(13:00~16:00)

  • メンバー1
    • /api/chair/notification、/api/app/notification のクエリ改善
    • /api/chair/coordinate で INSERT → SELECT みたいな処理を改善
  • @NotFounds
    • 距離計算を事前にすることで /api/owner/chairs の計算改善
    • Payment に重複リクエストを送らない → 効いてるのか分からん
    • マッチングレートを 0.5 から 0.4 → 変化なし。むしろ悪化?
    • Nginx/Puma 周りの秘伝のたれ対応 → 変化なし
    • クーポン適用周りの最適化
    • MySQL を別サーバーに移行リベンジ → 一瞬8533まで上がるもベンチの度に下がる 挙句、クリティカルエラー連発で心が折れそうになる

気づきフェーズ(16:00~)

ベンチ結果を見てユーザーの評価が一向に上がらない(すべて不満100%)なことに気づく → マッチングじゃね?となり改善を行う

  • メンバー1
    • /api/app/rides の N+1 改善
    • マッチングでの椅子の空状態の判定処理改善 → 椅子
  • @NotFounds
    • マッチングで speed が速いやつを割り当てる → なんかデッドロック状態になってエラーになった マッチングで rand * speed でソートして割り当てる → 評価の椅子の速さのところが改善した(気がする)
    • MySQL の問題は一向に解決せず、時間が解ける

最終調整(17:00~)

  • メンバー1
    • マッチング修正
    • ログを切る作業の準備
  • @NotFounds
    • 最後の賭けで複数台構成(Nginx/App+App+MySQL)に挑むも /api/initialize でエラー
    • Nginx/App + MySQL の原因調査

最終的に問題が解決せず、原因特定のためにこれまでのほとんどの修正を revert したうえで一台構成で終了

KPTA

Keep

  • Ansible で同時にサーバーを操作したのはよかった
    • 正直 Ansible にこだわりはなかったが NewRelic など重いのを入れるのは楽そうだとおもった
    • しっかり書いておけば timeout とかも簡単にできるのはよいと思った
    • ファイル操作と標準出力を取ってくるのはやりづらい...
  • 試行錯誤のために2週間ほどほぼ毎日 AWS で環境作って壊すをやったので、これでも大分障害耐性がついた
  • スコアには影響しなかったが、alp/slp計測→改善→alp/slpが改善されていることの確認はできていてよかった
  • 事前に用意していた ActionLogger (ユーザーの行動分析補助)や InMemoryCache は一応生かせて良かった

Problem

  • デプロイ・インフラ回り
    • Ansible の実行が思ったより遅い(Gather Fact がつらい)
    • Ansible を使ったことによりデプロイ処理が俗人化してしまった気がする(メンバーが使いやすいようにするところまでできなかった)
    • デプロイスクリプトはかゆいところに手が届かず、結果として複数台構成の失敗につながった可能性がある 三台それぞれに対して Nginx/MySQL/Application の ON/OFF まで管理できなかった
    • デプロイ→(ベンチ)→ログ解析はコマンド化していたので楽ではあったが、自分がブロックしてしまったのでよくない また、地味にログの投稿が面倒で時間が取られた
    • alp/slp の diff を使ったが、main との差分ではなく、ひとつ前のベンチの結果との比較になったので正確な比較ができなかった(featureブランチ同士の比較になったりした)
    • MySQL の slow query の threshold がうまく決められず SlowQuery の解析ができないときがあった(ログが吐かれすぎる)
    • どのくらい Query が投げられているのか計測できておらず、MySQL の負荷の原因が突き止められなかった
    • 複数台構成は練習不足を感じた
  • 実装まわり
    • Ruby で雑に非同期処理する方法を作れていない(Sidekiqの導入は負担が大きいので検討しなかった)
      • Go だと goroutine 投げて先にレスポンス返したり、Node でも await せずにレスポンス返すみたいなことができる
      • 練習で Thread 投げっぱなしにしてみたりしたけどうまく動かなかった
        • なんか書き方がよくなかっただけな気もする
      • SQL でできる処理なら TRIGGER 使うのがよい?
    • 細かいミス(SQL の SyntaxError や Ruby の nil 参照)などは競技耐性不足を感じる
    • 型のセットアップは行ったがエラーが多すぎて通すところまでできなかった
      • TypeProf 使いたくなった
    • (去年は Go だったけど)今年は Ruby の方が慣れているからという理由で選んだが、SQL と戯れる時間が長くてどっちでもいいのでは?となった
  • その他
    • 試行錯誤が足りない
      • 仕様で出てくるパラメータの調整などは全然できなかった
      • 手元で修正→デプロイ→ベンチ→エラー→ログ調査だと改善が遅いのでどうにかしたい
    • 点数に直結するコアな部分にたどり着くのが遅い
      • レギュレーション理解
    • エラーが出たときにちゃんと対策しておらず、どこでエラーが出たのかわからなくなり全部を戻す羽目になった
    • 事前に用意していた Ruby 関連の静的解析や型周りのセットアップスクリプトが1ファイルにしか対応していなくて使えなかった

Try

  • ログの投稿は自動化したい。もしくはダッシュボードにしたい
  • ログはコミットグラフに紐づけられるようにしたい
  • 1コマンドでデプロイ→ベンチ→計測までできるのが理想...
  • システムのメトリクスを見ながらの改善も大事だがベンチの結果とマニュアルをよく読む
  • 仕様に出てくるようなパラメータは極端な値を試してみて挙動を確認する
  • データ不整合が出てきた場合ちゃんとロックされているか/トランザクションが張られているかを確認する

Action

  • クリティカルエラーの原因は明らかにしておきたいので後日振り返りとしてバグ修正をやる