#isucon チーム「シン・ウー馬場ーイー2」でISUCON12予選に参加し2位で予選突破しました

今年も例年通り @matsuu@ishikawa84g と参加しました。

一昨年のチーム名は「ウー馬場ーイーツ」。去年は「ウー馬場ーイー2」。
今年は「シン・ウー馬場ーイー2」。

今年は @matsuu が "予選参加権つき個人スポンサー" を購入してくれたので予選参加申込競争は回避。便乗させてもらいました。

結果は一般枠2位で本選進出!
ISUCON12 オンライン予選 予選結果と本選出場者決定のお知らせ : ISUCON公式Blog

役割分担はこれまたいつも通りこんな感じ。

  • @matsuu バリバリ実装する前衛
  • @ishikawa84g サイトやレギュレーションやコードやログやDiscordを見る情報官
  • @netmarkjp 司令塔

アプリはgo実装で、競技時間終了時点のスコアは75800。 ベストスコアは76535。

最終スコアは75800でした。 ISUCON12 オンライン予選 全てのチームのスコア(参考値) : ISUCON公式Blog

ソースコードはGitHubに都度pushしてます。

matsuu/isucon12q

なお今年は #ISUCON本 の著者のひとりとして参加する初のISUCONでした。

なんというか妙なプレッシャーはあったものの、結果的に著者がいるチームが1位・2位にランクインしてホッとしました。

予選突破できた理由

(もちろんチームメンバーの連携、それぞれの担当の遂行力は重要です。そのうえで、なぜスターエンジニアを擁するチームよりうちのチームが安定して結果を出せているかという話)

これはISUCON本にも書いたのですが「ボトルネックとだけ向き合うこと」に尽きます。

今回で言うと、SQLiteをやめるべきかどうか最初の段階で検討して判断する思考法だと安定した結果を出すのはものすごく難しいと思います。わたしには無理。

実装のあるべき姿に向かって走り切る一発勝負なギャンブルは本選の戦い方だと思います。

予選でも実務でも、ボトルネック解消をコツコツ積み重ねるのが、成果を出して楽しむコツじゃないかな。

作業環境

今年も各自自宅で。

音声と画面共有はDiscordです。

わたしはディスプレイ3枚体制。

  • M1 MacBook Pro本体ディスプレイ:チームメンバーに共有
  • 外付け32inch:調べ物などもろもろ(メインディスプレイ)
  • iPad Pro 12.9(Sidecar):Discord

特に問題なく快適に作業できました。

振り返り

今回もとっても良問でした。 ポータルもストレスフリーで素晴らしかったです。 とても楽めました!

  1. kataribe・pt-query-digest(go-mysql-query-digest)・top・dstatを使ってボトルネックを探す
  2. ボトルネックに対処する
  3. スコアがあがる

今回もGitHubにスコアつきcommit historyがあるので振り返るのがらくちん。

Commits · matsuu/isucon12q

※とはいえ記憶違いで事実と異なることを書いてるかもしれません

ISUCON初回から参加してる勝手知ったるチームメンバーなので、さすがに役割分担で戸惑うことはありません。

序盤

まずはとにかくしっかりレギュレーションを読む。 @ishikawa84gが重要なところ・アプリの動作状況・利用方法などをパワポにまとめてくれました。

チーム内では事前に「Rustで挑戦したいね」と話していたため、まずはRustに取り組みます。

まずtop、dstat、kataribe、go-mysql-query-digest、mysqltunerでモニタリングできる環境を整えたら試合開始です。 ベンチマーカーを実行したところネックはMySQLのCPU利用率でした。 mysql-query-digestの結果を見るとID生成処理がトップだったため、コードを読んで生成ルールをもとに"重複がなければOK"と判断してUUIDv4に変更することにしました。

crates.ioでUUID用のライブラリを探して、Cargo.tomlに書いて、neovimでコードを書いて、、、とやっていたところ、サーバがハングアップ。 どうもrust-analyzerを動かすにはメモリが足りなかったようです。 たびたびこうなってはたまらない、ということでRustは断念しGoに切り替えます。

go実装の初期スコアは2561。

改めてUUIDを実装して3831。

引き続きMySQLなのでもういっかいmysql-query-digestを確認。

INSERT INTO visit_history ... がネックだったので、コードを読んでplayer_idごとに最初の1レコードでよいと判断して別テーブルを作成。 そちらを使うように変更しました。

これで4936。

このあたりで12:00を過ぎたのでお昼ごはん。 最初Goに切り替えたあたりでどうなることかと思っていたものの、少し先行きが明るくなってきました。

中盤

構成は変えずに引き続き。 ボトルネックはDiskIOとCPUだった(と思う)なので、(SQLiteのまま)インデックスを貼ったり、 visit_history の不要なレコードを触らないようにしたり...

そうこうしているうちに14:00過ぎ。 DiskIOがボトルネックになり、いよいよ目を逸らさずSQLitesと向き合う時が来ました。

……ということで、どうしたらよいか考えたものの、、、SQLiteのままではOODAループを回すための最初のO(Observe)ができない(やりかたを知らない)。

これはもうどうしようもないね、ということで、レコード数的にいちばん激しそうなテーブルからMySQL化。 同時にDiskIO高速化に効くMySQLチューニングパラメータを投入しました。

14:45にひとつ、15:21にすべてのテーブルをMySQLに移行。 これでスコアが15672。

後半

こうなるとmysql-query-digestで分析できるようになり、またいつものOODAループが回り始めます。

その後コツコツ改善して、16:54にスコアが44051。

終盤

CPUボトルネックなので、複数台構成に。

  • 01: nginx・app
  • 02: MySQL
  • 03: (使っていない)

1台構成のときMySQLが110%くらいだったので、まずはMySQLを外だし分割してまずは2台構成に。 だいたいそのままの比率でスコアが伸びて63991。

top、dstatを眺めていたところ @matsuu がベンチマーク終盤でNginxとappの負荷が非常に高くなることを発見。 そんなわけで3台目はappとして使うことに決定。

  • 01: nginx・app
  • 02: MySQL
  • 03: app

17:41にスコア72760。これがベストスコアでした。

ここまでくると、あとは細かい調整をしながら再起動試験をして終わり。

再起動後にベンチマーカーを実行して75800でフィニッシュでした。

まとめ

@matsuuも書いてくれたのでご参照ください

ISUCON12予選にシン・ウー馬場ーイー2として参加し、2位で予選突破しました - Gマイナー志向

齟齬がある場合、たぶん @matsuu のほうが正しい。

さて。本選がんばるぞ!

Event 

See also