Aim Developer

i.e. blog

ISUCON8予選の1日目に参加した記録です。ISUCON自体初参加でしたが運も良く予選突破することができました。 最終スコアは40,069で、全体で14位くらいのようです。

謝辞

今回はミクシィさんのオフィスから参加しまして、快適に作業することができました。素晴らしい環境を提供したいただきありがとうございました。

ISUCON初参加でしたが、本当にすごいですね。課題もおもしろかったですし、ベンチマークもポータルも高速でトライしやすかった。むしろこの環境をどうやって作るんだ……というのを感じました。みなさまありがとうございました。

メンバーのTwitterから勝手に拝借したやつ

準備

今回は前職の同僚と参加しました。地味に大変だったのが当日の作業場所の確保。誰かの現職のオフィスで参加できれば楽だったんですが、それぞれのオフィスが、立地、周辺環境、作業スペース などの理由でイマイチでした。渋谷でコワーキングでも予約できないかな、と思ってたところ、たまたまミクシィの方が去年作業スペース提供されてたのを見つけました。調べてみたら今年も会場提供される予定で、そこに参加できたので作業場所はなんとかなりました。助かった・・・

そもそも、ISUCON楽しそうだし1度出てみたいねーってくらいの温度感だったのと、チームメンバーの1人が技術書典の執筆とかでかなり忙しそうだったので予行練習とかはしませんでした。とは言うものの言い出しっぺなので何かやっておかないと、ということで1週間前にISUCON7をやっておきましたが、これはやっておいて本当に良かった。

ローカル環境作るのが思ったよりだるいとか、New Relicを使おうと思ったけど入れるのに変にハマったりっていう環境面もそうですが、なんとなくでN+1の修正をしてもスコアが大して上がらない場合もあるからちゃんとボトルネックを見つけてそれに対して作業していくべき、という体験ができたのは有益でした。 その辺の感想とか戦略的なものをFBグループで共有したものの、当日ちっとも参照しませんでしたけど。

ちなみにNew Relicの導入にハマったのはPHPのパス的な問題です。rootユーザでNew Relicをインストールする必要があるものの、ISUCONの環境だとrootユーザからphpコマンドにパスが通ってないのでパス通してあげないとインストールできません。

当日NUboard持っていこうと思ったけどすっかり忘れましたね。Discordで見かけたNotionっていうサービスがちょっと良さそうだった。

当日

結果的に予選突破できた、とはいえ個人的には運が良かった部分が大きいかなと思ってます。そのくらい特別なことは特にしていません。

うちのチームのメンバー構成は

前職がPHPの会社だったのもあり、自然とPHPでいくことは決まってました。自分はPHPerなのでPHP書いたり、ツール入れたりログ管理したり的な下回りをやりました。

なんとなくで空気を読んでいい感じに、で作業できるメンツだったので、チームとしての方針をふわっと示せばなんとかなるだろう、という感じで、そういう方向にハンドリングできればいいなーと考えながらやってました。そういうマネージメントが出来ていたかはさておき。

初めにやったことは

クエリログで実行されるクエリの一覧を取得してざっくりEXPLAINかけて、どんなクエリが流れてるか、インデックス状況はどうか、sql_modeがどんな風になってるかなど把握します。特にツールを使わずにsort, uniq, grepとかでなんとかしてましたが、この辺のログ収集はもっといい方法が絶対あるよなーとは感じてるところですね。

Discord見てたらkataribeっていうツールがアクセスログ解析で人気みたいですね。使ってみるか。

一通り環境がそろってベンチが回せるようになったら重そうなところからやりたいところを分担して改善していきます。 とはいえ午前中はなかなかスコアが上がらなくて少し焦りました。たしか午前中は1000前後くらいしか出てなかった気がします。

自分がやったところで言うと、とりあえずRedisを入れて、sheetsテーブルのデータが全体を通して更新されそうにないのでRedisに移し替えたり、不要なクエリ発行を止めたりしてました。

午後からインフラ側の環境が少し整ってきて、WebとDBを分離したり、h2oよりもnginxの方が慣れているということでnginxに載せ替えたりしてもらいました。DBもMySQLに載せ替えようかと思ってたけどMariaDBのままだったと思います。

Webを2台構成にした方がパフォーマンス出るんでしょうが、用意するのが大変そうだったのでWeb1台、DB1台、検証機1台にしてました。どうやらh2oだとhostsを使って複数台構成できるらしい?のかな。 複数台構成にしたタイミングですぐにinitializeで呼ばれてるDB初期化の処理をいじらないといけないことが若干ハマりポイントな気がしますが、幸いすぐ気付けたのは良かった。無駄に頑張る必要も無かったので、init.sh のmysqlコマンドに -h オプションつけてDBサーバに向けるだけで済ませました。

その後もIndex貼ったりとかいろいろしてましたが、そんなにスコアが伸びなかったですね。変だなーと思って後回しにしましたが、この理由に最後の最後に気付けて良かった・・・ とりあえず重そうなところに着手していくことにして、get_eventに着手。ここから呼ばれるreservationsへのクエリがかなりボトルネックになっているようだったので、N+1を改善してみたところ、1000程度だったスコアが1万近くまで伸びました。本当はこの関数の処理をちゃんと理解して適切なロジックに変更するべきなんですが、そこはチームメンバーに任せっきりだったのでほとんど理解してません。

その後もチームメンバーの改善で伸び続けて、気付いたら2万を超えていくくらいになりました。

全体でのCSVの出力がかなり遅いのをどうしようかな、と思ってましたが、アクセスログ的には1回呼ばれるくらいだったのでそんなに影響しないだろと放置することに決めました。 DBは遅くなくPHPが遅かったんですが、大量のデータを配列なりオブジェクトに変換するところで時間が使われているのは経験上想像できてましたが、後回しにしました。ここはgenerator使えばだいぶ改善するのでは…という案があったので、問題が公開されたら試してみたいところです。

その他、HTTPリクエストが多いのでassets系をまとめようとしてJSをminifyしてらDOMが変更されたとエラーになったりとかやってました。 そもそもリクエストがなくしたい、ということでキャッシュ周りの設定をいろいろしてみたけれどもうまくいかなかった。この辺やっぱり理解が怪しいな・・・

時間も無くなりできそうなことも減ってきて、まぁ2万超えたしメンツは保てたから良かったなんて思いながら確認してたら、ラスト15分くらいで大事な問題に気付きました。 どうしてここを見たのか覚えてませんが、ベンチ後のDBサーバでインデックスが効いてないことが分かりました。 show create table reservationsしてみたらデフォルトのindexしか無いんですよね。
そうですね、自分がindexを貼るとき、DBサーバ上で直接ALTERを実行してたので、initializeのDB初期化で全部消えるんですよね。
それまでデフォルトのインデックスでやってたということか・・・
土壇場でこのことに気付き、init.shからALTERを実行するようにしてからベンチを回したらスコアが倍増してまさかの4万超え!最後にHighestスコアを出して終われました。

ただ、ベンチを回すとDeadlockで500エラーがかなり出るんですよね。この辺もどうにかしたかったんですが時間切れ。 今回は500エラーがスルーされるのでそのままにしましたが、実際のアプリではこの品質じゃダメですねw

途中経過で見てた感じだともっとハイスコアを出してるチームがたくさんあったので、まぁ予選突破は難しいだろうねと思っていたのですが、まさか突破することができるとは。
たしか去年はPHPでの予選突破がいなかったはずなので達成できてPHPerとして嬉しいですね 😎

勉強し直して本戦がんばります!