ISUCONでAIを最大限活かすためのツールを作りました #isucon14
チーム「百万円ドリブン」のnakarioです。今年もISUCON参加してきました!今回は参加時にやったことの紹介はやめにして、私が作成したISUCONでAIを最大限活用するためのツールを紹介したいと思います!
……本戦の結果がダメダメだったからです!
AI最高!
AI良いですよね!質問に答えてくれたり、ちょっとしたコードを書いてくれたりします。今回私はGitHub Copilotに課金してVS CodeでCopilotを使えるようにしました。私がSQLクエリを書くといつもsyntax errorで怒られるのですが、Copilotに書いてもらうと私よりはマシなコードを書いてくれます!それにGoのif err != nilなどの面倒なコードもサクッと生成してくれるので気持ちよくコーディングできます。
その中でもCopilot Editの機能は素晴らしいです!指示に従って既存のコードを書き直してくれるので、上手く使えばN+1の解消やキャッシュの有効化などが一瞬で出来上がります。ミスをすることもあるとはいえ、大筋あっていれば手で修正することも可能なので、大幅スピードアップに貢献してくれています。
そんなCopilot Editですが、不満が一つあります。ファイル全体を書き換えるので、長いファイルをEditすると結構時間がかかることです。試しに今回のapp_handlers.goに対して「appGetNotification関数をhogeにリネームして」とリクエストしてみると、だいたい54秒かかりました。appGetNotification関数自体はファイルの真ん中より下の方にあるので、編集マーカーが上の方から流れていくのを見ている時間は虚無になります。
そこでファイルを細かい単位に分割することでCopilot Editの時間を短縮することにしました。手で一個一個のファイルを分割していると時間がかかりすぎるので、自動でファイルを分割するツールを作成しました。
ISUCON13、久々の現地参加してきました(百万円ドリブン:11位)
今年もISUCONがやってきた!
こんいすー。チーム「百万円ドリブン」のnakarioです。今年もISUCONの季節になり、我々にとっては7回目の参戦を果たしてきました。ほぼISUCONについてしか書いていない本ブログの読者はISUCON関係者しかいないと思うので、ISUCONとはなんぞやみたいなのは飛ばしてさっそく感想から書いていきたいと思います。
久々の現地参加
コロナ禍の影響で長らくオンラインのみの開催となっていたISUCONですが、今年はLINEヤフーのオフィス現地参加抽選枠があり、ありがたいことに我々はその枠で現地参加してきました。オンライン開催のときにはチームメンバーで自宅に集まって参加していましたが、やはり他のチームと同じ場所で参加すると緊張感や高揚感、イベントに参加しているという実感が段違いですね!

最後の現地参加はミライナタワーのLINEオフィスでしたが、それからオフィスが移転したり、ヤフーと統合したりと前回とはガラッと変わった環境ですが、競技者視点ではモニターの貸与があったのでこちらの方が良かったですね。新宿駅ダンジョンよりも分かりやすい場所にありますし(チームメンバーの@Muratamは迷って3つぐらい別のビルに入ったりしてたみたいですが……)。
競技終了後にお酒と料理を楽しみながら感想を言い合ったり、941さんの配信を生視聴したり、二次会で他チームと合流してISUCONトークで盛り上がったりも含めて最高の一日でした!!
成績と反省点、問題に対する感想
今回は11位という成績で終えることができ、チームとしては予選敗退だった前回はもちろん、他の決勝進出回と比べてもなかなか良い成績を出せました。スポンサー企業様より「いい生活賞」も頂けたのでいい事づくしです。ですがチーム名にもある通り狙うは1位100万円なので、まだまだ修行が足りていないですね。
ISUCON本戦では各々の開発力はもちろんですが、本番ゆえの緊張からくる判断ミスや過集中の影響が無視できないというのが僕のこれまでの参戦歴からの学びでした。その点、今回は初動から優勝チームであるNaruseJunがぶっ飛ばしていたのもあり、自分でできることを着実にやろうと冷静になれていたのではないかと思います。例えば、後述しますが序盤のボトルネックの改善でしょうもない見逃しをしていたせいで大きくタイムロスしたのですが、その際も別の方法で解決することができないか考え直したり、メンバーに協力を要請したりなど、目の前のタスクにとらわれずにやるべきことをやる、ができていたと思います。後半も、実装は間に合いませんでしたが取るべき方針は間違ってなかったと考えています。ということで今回は、純粋に技術力・実装力で負けたと、認め難いですがそういうことになるという認識です。悔しい!我々のチームは毎年本番前に丸4日ほど使って過去回をおさらいしているので、定番パターンのN+1問題やDBのインデックスなどを解決する方法は理解しています。しかし、定番ゆえに手癖で解いてしまうことがあり、複数の解決方法を検討した上で最も効率の良い方法を選択する、ということを事前練習の段階で行っていませんでした。ゆえに本番でも愚直に手を動かす以外の手札がなく、最適な選択を行おうという意識があるにも関わらず実質的に取れる選択肢が一つだけ、という状況だったのかなと振り返って思います。今回は事前練習ではツールの整備などに時間を多めに割いていました。それは間違いではありませんでしたが、それだけでは足りないということが改めて思い知らされたといったところです。次回はN+1を解くにしても10個くらい手札を用意して参戦したいですね。
ISUCON13の問題の感想ですが、題材としてはどこかで見たようなライブ配信アプリだったので取っ掛かりやすさは大きかったです。独自のゲームなどが題材だとそのゲームを理解するところから始めないといけませんが、今回はすんなりアプリの概要を理解して競技に集中することができました。ベンチマーカーの待ち時間が大きかったところは気になりましたが、スコアは大きなブレもなく、改善を入れれば素直に点が上がっていったので気持ちよく取り組むことができました。こういったところを作っていくのも面白そうなので、早く我々も優勝して出題側にまわりたいです。
やったこと
- 10:30 3268点: 問題の理解や環境構築
- 11:00 各種ログやpprofの設定
- POST /initializeを叩くと同時にpprofを計測する仕組みを我々も用意していますが、ログの集計時にMySQLのrestartでアプリも再起動することに気付かず、自動集計の仕組みがうまく動かなかったので少し手間取りました
- 11:20 3,614点: DBの2台目への移動
- 11:40 9,257点: インデックスの作成、InterpolateParamsの有効化、DBコネクション数の調整
- 12:00 11,908点: 更にインデックスの作成、PDNSのTTL=120
- 12:40 20,100点: N+1の解消、DNS用のインデックスの作成
- 13:30 26,892点: icon_hashのキャッシュと304
- 前述の序盤のミスです。11時半ば頃から取り組み始めてここまでかかってしまいました。
- icon_hashのキャッシュ自体は割とすぐに実装できたのですが、ETagをレスポンスに含めているにも関わらずベンチマーカーがIf-None-Matchヘッダーを付けてリクエストを送信してくれず、ブラウザでの検証でも同様で、どこが間違っているのかわからない状態でした。
- 原因はETagの値をダブルクオートで囲まずにヘッダーに入れていたことでした。とほほ……。
- 13:45 27,168点: ライブコメントの走査対象を限定
- 15:00 34,025点: N+1、インデックス
- 15:30 43,924点: N+1
- 15:40 57,294点: slotの範囲検索をid検索に変換
- 15:50 69,737点: N+1
- 16:00 80,976点: livestream_tagsのキャッシュ
- 16:30 84,773点: tagsのキャッシュ、インデックス
- 16:40 104,645点: インデックス
- 17:30 111,401点: JSONのインデントをなくす
- 17:50 127,149点: 各種ログのオフ
16時くらいからDBの水平分割をメンバーに取り組んでもらっていましたが、あと一歩届かず3台目のサーバを丸々未使用のまま終了しました。DNSのDBを分けるなどすればもしかしたら5位くらいにはなっていたかもしれませんが、狙うは1位なので無駄なことをしないという選択をした結果です。僕がETagで手間取らなければ+1時間くらいあったはずなので、そこが一番の悔いですね……。手書きせずにライブラリを使ってもうまく行かなかったのですが、これはブラウザで検証した際の方法が良くなかったのかも。ChatGPTに質問しても見当違いの返事しかしてくれなかったので、やはり普段からサーバを書いて経験を積んでおくことが肝なのかなと思いました。あるいはCopilotに書かせるのも良いかもしれません。
最後に
今回のISUCONも大変楽しかったです。主催のLINEヤフー株式会社様、出題のさくらインターネット株式会社様、株式会社いい生活様はじめスポンサー企業の皆様、ISUCONの運営やコミュニティを支えてくださっている皆様、どうもありがとうございました!(あれば)次回もぜひ参加したいです!
ISUCON12予選突破できず無念
予選突破出来なかったけどせっかくなのでやったことを書きます。
僕はチーム「百万円ドリブン」では主にアプリケーションの修正をメインに担当していて、メンバーのaokabiとmurataはそれぞれnginxやmysqlなどのミドルウェア、なんか色々をメインに担当してもらってます。
スコアから書くと14205点でスコア再現性テストで失敗して失格でした。失格については再起動テスト含めて再現性の検証をやっている余裕がなかったので仕方ないとは思ってますが何がだめだったのかは謎のままです。
当日やったこととしては、いつもどおり一通りの計測手法を仕込んだあと、mysqlへの負荷が低すぎるということでsqliteの存在に気づいて、主にaokabiとmurataにsqliteのまま分散 or mysqlへの移行で水平スケーラビリティを確保してもらう方向で動いてもらいつつ、アプリケーションを改善していく方向でやっていました。
ただ、sqliteが通信のオーバヘッドがない分N+1が負荷になりにくいこととかが頭からすっぽ抜けていて、N+1を直したのにスコア変わらないなぁといった感じに空回りしていたなと後になって思います。また、sqlite3で動いていたwindow関数を使ったクエリをmysqlで動かしてみると「ONLY_FULL_GROUP_BYモードが有効だと非集計カラムを参照できない」といったエラーで動かないことが判明して、非効率的なサブクエリとのJOINでなんとか動かすみたいなことをしていたので、こういった情報をちゃんと予習できてなかったなと反省したり(むしろ何で今までのISUCONでこのエラーに遭遇しなかったのかが不思議)。
15:30ごろにaokabiとmurataがmysqlへの移行(/var/lib/mysqlの全置換によるinitialize時間の短縮)を完了してくれて、そこからは各々いつも以上のパフォーマンスを出せたのではないかと思いますが、さすがに時間が足りずスコアが伸び切りませんでした。mysqlサーバを3台用意しておいてinitializeのたびに向き先を変えて、今まで使っていたサーバの初期化を走らせておくっていう副案も抱えていたのでそれをやっても良かったかもですね。競技中はもっとまともな方法で解決できるはずと思っていましたが、たとえ異常な作戦でも有効ならばチャレンジしていく勇気がなければ百万円には届かないのかもしれません。
ISUCON11-priorやってみた
予選が近いので練習戦として isucon11-prior をチームでやってみました。解説記事は出ていないようだったので、答え合わせはできてませんが8割くらいは対処できたのではないでしょうか。
構成は以下のようにして、最終的に30,000点を超えるくらいのスコアで終了しました。
やったこと
- 計測環境構築
- デプロイスクリプトの用意
- N+1の解消
- MySQLのCPU負荷がネックなので、スロークエリログをもとにgetReservations、schedulesHandlerに仕込まれているN+1を解消します
- この時点でbenchサーバの移行が完了した上で4,000かな?
- nginxでstatic fileの配信
- aokabiがやってくれました
- スコアは大きくは変化しなかった(はず)
- indexの追加
- タイミングが分からないけど多分このあたり?
- overbookingの解消
- schedulesHandlerの挙動をドキュメント通りに変更
- ここらで、原因は最後までわからなかったのですがスコアが4000点付近に張り付いて動かなくなる現象が発生
- GETリクエストばかりでPOSTが来なくなっており、得点計算かユーザの挙動に原因があると考えてドキュメントを読み漁る
- 実装とドキュメントの齟齬を発見し、一般ユーザには空きのあるスケジュールのみを表示するように変更
- 実装したものの点数に変化がなかったが、いつの間にか8,000点くらいになっていたので実は効果があったのかも
- DBのmax connectionを設定
- 点数が伸びないので場当たり的にやった
- reservedカウントの管理オンメモリに移行
- indexの追加とかもあったかも
- 12,000点くらい
- interpolateParams=true
- 14,000点くらい
- 2台構成に移行
- アプリとDBで分割
- parallel=20に変更したのもここ付近?
- 24,000点くらい?
- reservedのオンメモリ化をすすめる
- 30,000点ちょい
- (失敗)reservationのbulk insert
- 一定間隔おきにためておいたreservationを一括でinsertする実装を追加しましたが、ベンチとの相性が悪かったのか点数は変わりありませんでした
- (失敗?)json Encoderの置き換え
- 公式のencoderとほぼ変わらなかった
感想
練習用に作られたものとして基本を抑えつつ、ボリュームもしっかりあってとても楽しめました!UIやパスワードまわりは大胆に手を抜きつつ、ISUCON要素をぎゅぎゅっと濃縮したような感じでした。
あたかも私がボトルネックなので変更してください!と言っているかのようにドキュメントに書いてあるID生成の部分は触ってないですし、ベンチサーバー側のメモリが溢れそうで負荷が増やせない感じだったのでまだまだ上は目指せそうです。こんなにやりがいのある問題を提供していただいて、本当に作問者の方々には感謝してもしたりないくらいです。
さて、まずは予選を突破できるよう、早寝早起きを頑張ろうと思います!
学生枠、今までありがとう #ISUCON9
ISUCON9予選突破できたのでブログ書いてます
3年連続ISUCON本戦出場!!! https://t.co/2wzmSTVMiS
— nakario (@nakario_jp) 2019年9月8日
今年から学生枠が無くなって、去年の予選の順位的に本戦出場はぶっちゃけ無理だと思ってました
盛大に砕け散って学生枠の再開を声高に主張するつもりだったのに……
本題ですが、僕はチーム(百万円ドリブン)の中では一番Goに慣れているということもあってアプリのコードの変更をメインで担当しています
やったことを時系列順で簡単に書くと
- pprofを仕込む(10:20)
- getCategoryByID のDBアクセスをなくす(12:40)
- getNewItems, getNewCategoryItems のN+1を解消する(14:30)
- なんか不要そうな FOR UPDATE を postBuy から消す(17:20)
- 上手くいくか怪しかったけど getItem からDBアクセスをなくす(17:40)
- もっと FOR UPDATE を大胆に消してみる→エラーが出たので大急ぎで戻す(18:05)
という感じです
最後ので戻せなかったら予選敗退してたのでマジ危なかった
つらつらと書きましたが、今回の最大の勝因はbcryptの計算専用に2台のサーバを使うという大胆な戦略を提案・実装してくれたチームメンバーのaokabi & murataにあると思ってます。感謝。
その他良かった点
- キャンペーン還元率に早い段階で気付けた(ISUCON8本戦でSNSシェア率による負荷コントロールの仕組みに気付けなかったので気をつけてた)
- pprof、pt-query-digest、netdata、alpなど複数のソースからボトルネックを判断した(過去問練習で見えてるボトルネックと原因が別の場所でハマった)
- 定期的に声掛けして方針を立てた(ISUCON7本戦で何時間も一人で悩み続けた反省)
- 大きいホワイトボードを用意した
- カレーを食べた
こうして見ると、学生枠で本戦に出場させてもらえた経験がとても活きているので、これからISUCONを始めようという人たちのためにも学生枠が廃止されたのは残念です
学生枠、今までありがとう