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テーブルに列追加でやってるところが多くてトリガーは悪手だったかなーと思いました。少なくとも自分たちはあまりスコアが向上しなかったので、テーブル結合は避けた方が良かったのかもと反省しました。
打ち上げ
とりあえずお疲れ様ということでお肉食べに行きました。めちゃくちゃ美味しかったです。
感想
悔しい結果でしたが、今年の問題はめちゃくちゃ面白くて楽しかったですね。
ボトルネックがユーザーのシナリオを意識する必要があるのが良かったです。マニュアルの読み込みの必要性を感じました。
上位の参加者のリポジトリを見て、自分のエンジニアとしての実力不足を非常に感じました。めちゃ悔しいので来年頑張ろうと思います。
おしまい。