ISUCON 14に参加しました

yamaday
·

ISUCON 14に参加しました。今回は大学の先輩のhika7719と組んでチーム「どら焼き」で出場してきました。

結果

最終スコア0でFAILで終わりました。ベストスコアも3,000点程度だったのでスコアを全然伸ばせず大敗でした。非常に悔しいです。

チームのスコアグラフ

ISUCON 14の問題概要

今年の問題は「ISURIDE」というライドチェアサービスでした🪑

問題説明についてはオンラインライブ中継のアーカイブでも観れます。

アプリケーションの詳細は上記マニュアルにある通りですが、ざっくりとまとめると以下のようなお題でした。

  • 「ISURIDE」というライドチェアサービス

  • 主要なアクターは「ユーザー」と「オーナー」の2種類

  • オーナーは椅子を運用する企業・個人。オーナーは収益が上がると更に椅子を導入する。

  • ユーザーは椅子を利用して移動をする人。アプリから目的地を入力して配車リクエストを出す。ユーザーには招待コードでの割引特典などのクーポン要素あり。

  • 椅子は自動運転で動作し、配車リクエストを出したユーザーに対し、椅子をマッチングさせて椅子を到着させる必要がある。

事前準備でやったこと

事前準備では当日に慌てないように初動のアプローチの合意といくつかの技術的なタレを用意しておきました。

  • 初動アプローチの合意

    • インスタンスの作成が完了したら1台で初回ベンチマークを回す。

    • GitHubへのPull/Pushを行うためのsshキーの生成をする。

    • 初回ベンチの情報をもとに複数台構成の配置を決める。

    • アプリの言語はPythonを利用

  • 技術的なタレの用意

    • Nginx/MySQLのコンフィグファイルの用意

    • Makefileにログローテーションのタスクを用意

    • APMツールの素振り

    • alpを使ったNginxのログ確認の素振り

    • MySQLのスローログ確認の素振り

当日の流れ

9:00 簡単に打ち合わせ

当日の朝9:00からDiscordを繋いで、初動の役割分担を再確認しました。

9:30 YouTube オンラインライブ中継視聴

問題説明を聞くためにYouTube オンラインライブ中継を視聴しました。

10:00 競技開始 - セットアップ

自分は課題の確認、hika7719にAWS環境のセットアップを実施してもらいました。ssh_configはhika7719側で作って共有するスタイルでやりました。

10:14 初回ベンチマーク

初回のベンチマークはアプリ実装はGoのままで回してスコア1,158でした。

ベンチマーク回しながらtopで負荷を確認し、MySQLが重いのがすぐにわかったので、まずはMySQLを別インスタンスに移動する構成で行こうと話しました。

10:39 アプリをPythonに切り替え、MySQLを別インスタンスに移動してベンチマーク

アプリをPythonに切り替えて、MySQLを別インスタンスに移動しました。

この構成でベンチマークを回してスコア728…。

10:44 MySQLのスロークエリログを仕込んでベンチマーク

MySQLのスロークエリログを見るためにコンフィグを仕込んで、再度ベンチマークを回しました。この時のスコアが584でした。スロークエリログを仕込んだので下がるのは想定内でした。

12:31 インデックスを張ってベンチマーク

スローログをチェックしながら、各テーブルに足りていないインデックスを張りました。

インデックスを定義する作業量はそこまでではなかったのですが、ベンチマークを回しながら、なぜか「椅子がライドの完了通知を受け取る前に、別の新しいライドの通知を受け取りました」の致命的なエラーが発生し続けてベンチマークが通らず混乱していました…(そうしてこの問題が発生する根本原因は17時頃にわかるという…)。

とりあえずインデックスを張ったことでスコアは2,871になりました。

13:00 分担作業からの魔の時間の始まり…

少しお昼休憩を挟んで、分担して作業を進める方針にしました。

  • SSE(Server-Sent Events)対応(hika7719)

    • ベンチマークのライドの完了通知周りでの致命的エラーに対し、DBのロックなどをしていましたがなかなか改善せず、マニュアルにもSSEでも良いって書いてあるしやってみるか〜という流れになりました。

    • 正直、順序整合性を保ちながらat least onceも守ってSSEを実装するのはハードル高いなと思いながらも分担しました。

  • スロークエリの改善(yamaday)

    • 自分はスロークエリログで明らかに遅いSQLの改善とalpで上位に上がってたエンドポイントのチェックを担当しました。

14:11 chairsのスロークエリの改善

発行回数はそこまで多くないものの最も遅かった、オーナーが実行するchairsテーブルに対する以下のSQLクエリを改善しました。

改善アプローチとしては、椅子の総移動距離の算出がネックになっていたので、椅子の移動距離を別テーブルで記録する方針でやりました。

椅子は移動する度に、位置情報であるchair_locationsテーブルが更新される仕様になっていたので、chair_locationsテーブルの更新に対して、トリガーを使って別テーブルのchair_distancesテーブルに総移動距離を記録するようにしました。

SQL自体はchairsテーブルとchair_distancesテーブルを結合する形で変更しました。

スロークエリ自体は改善したのですが、ベンチマークを回してもスコアは2,513と改善しませんでした。

その後、EXPLAINをして少しインデックスを張り直しましたがスコアは2,782までしか戻らず…。

15時頃〜 マッチングロジックの修正を開始

ベンチマーク回しながらtopで見てもDB側に負荷があまりかかっておらず、スロークエリ以前に根本的にアプリに負荷を増やすアプローチを取る必要があるなと気づきました。

  • ユーザー数を増やすためにライドの評価を向上させる

  • オーナーが追加の椅子を導入できるように収益を上げる

この部分がベンチマーカーのスコア向上に必要だったため、マッチングロジックを修正する必要がありました。

初期実装は椅子が未割り当てのライドに対し、割当可能な椅子をランダムに割り当てる方式になっていました。

とりあえずランダムはやめて、ユーザーの配車位置に対し、最も近い椅子を割り当てる方式にしました。

しかしながら、スコアは上がらず…。

しかも最も近い椅子の判定のためにMySQLのPOINT型への変換を実施したりしたのですが、今回の問題設定ではマンハッタン距離での計算かつ緯度・軽度が範囲外の値が入ってくることがあり意味なかったです(↓マニュアルの注意書き)

注:架空の世界なので、緯度が±90度を超えることがあります。経度も±180度を超えることがあります。

17時頃 DBインスタンス側で動いているアプリが邪魔していることに気づく

もともと通知周りの致命的エラーでベンチマーカーが落ちることがあったのでSSEにアプローチしていたのもあったのですが、そもそもDBインスタンス側でアプリを動かしっぱなしにしていて、インターナルなマッチング処理がDBインスタンス側でも実行されていました…。この処理が邪魔でベンチマーカーが不安定な挙動をしていたことに気が付きアプリを停止しました…(遅い)

停止してからスコアが安定して計測できるようになりました。

そしてやはりSSEの実装は難しいという形になりました。

17時半〜競技終了 マッチングロジックの修正(再)

hika7719と一緒に最後にマッチングロジックの改善を再度、試しました。

そもそも1件づつ処理しても最適なマッチはできないと判断し、マッチング対象のリクエストを複数件取得して、割り当てるアプローチを取りました(遅い)

しかしながら残り時間も少なく、マッチングロジック単体でのデバッグも上手にできず、最終スコアは0で終了しました…。

反省会

Discordの#randomチャネル見ながら、色々と話しました。

自分たちはマッチングロジックの改善で近い椅子重視で距離でソートするかスピード重視で椅子の速度でソートするかなど話していたのですが「距離/速度でソートした」という件で頭の硬さを感じました。冷静に考えれば出せたアイディアで非常に悔しかったです。

あとSSEはやっぱり難しかったですね。捨てるべきところでした。

椅子の移動距離の保持はchairsテーブルに列追加でやってるところが多くてトリガーは悪手だったかなーと思いました。少なくとも自分たちはあまりスコアが向上しなかったので、テーブル結合は避けた方が良かったのかもと反省しました。


打ち上げ

とりあえずお疲れ様ということでお肉食べに行きました。めちゃくちゃ美味しかったです。

感想

悔しい結果でしたが、今年の問題はめちゃくちゃ面白くて楽しかったですね。

ボトルネックがユーザーのシナリオを意識する必要があるのが良かったです。マニュアルの読み込みの必要性を感じました。

上位の参加者のリポジトリを見て、自分のエンジニアとしての実力不足を非常に感じました。めちゃ悔しいので来年頑張ろうと思います。

おしまい。

@wf_yamaday
日常について📝