ISUCON6に id:uzulla さんと id:moznion さんとチーム「[=======> ] 80%」で出場して、予選通過できず敗退しました。
残念…。
前日までの準備
かくかくしかじかでこの3人で出場することには決まったものの、とにかく2人とは一緒に仕事もしたことないし 顔合わせして練習が必要ですね、ということで8月末と9月前半、2回集まって過去問を使って練習会をした。
まず使用言語。2人はPHP強いけど僕は1行も書いたことないし、Goは3人ともある程度は出来そうだけどまぁやっぱりPerlが(最近は全然さわってないにしても)業務でも使って慣れているし安心かな、ということでPerlで。
役割分担としては、
- インフラ・ミドルウェアまわり: id:uzulla さん
- Webアプリのコード変更: id:moznion さん + 僕
という感じで。
インフラ・ミドルウェア周り完全にuzullaさんにお任せすることになってしまい、負担が大きくなかなかアプリのコードまで見てもらう余裕を作れず申し訳なかったです。ベンチマークまわしてログを解析してボトルネックを見つけ出す、とかもほぼ任せっきりでやってもらってしまいましたが とにかくそのへんのオペレーションやチューニングは完全に信頼できてとても良い仕事をしていただいて、本当に感謝しております。
アプリのコードはmoznionさんと練習もして息を合わせて作業することができて良かったです。お互いローカル環境でも動作確認程度には動かせるようにしつつコードを読んで動作・仕様を把握し。ログからボトルネックを確認できたらまずそこを解消するためにどう変更するか方針をちゃんと相談し、分担できるところは分担して作業。出来るだけちゃんとプルリクを作ってレビューした上でmerge。お互い無駄なミスや手戻りすることなくスムーズに出来たんじゃないかと思っています。
場当たり的に重そうなところを変更していくのではなく、しっかりログを解析してボトルネックを把握して潰し効果を確認し、また次のボトルネックが分かったらそこを潰す、という流れを繰り返す。2回の練習でその感覚はかなりついて手応えはあった(し、実際当日も効果あった)ので 練習会は本当にやっておいて良かったと思います。
当日・前半
気兼ねなく声出して議論しながら作業できる会議室を確保できたのでそこに集合、準備万端で競技開始。
uzullaさんがAzureについてはかなり使い込んで詳しくなってくれていたおかげで最初のサーバ立ち上げや初期セッティングは練習通りでスムーズに。
こちら側も練習通りにコードをGitHub repositoryに入れてローカルで環境作って…(Perl 24.0なんて入ってなかった!!1 plenv installからだ!!!)
アプリ・DBが2個に分かれてるのか…面倒だな、はやめに統一できるならしてしまいたい、ということでボトルネック計測と並行でその作業から。 isutarの方はごっそり移行できそうだったので慎重に変更しつつisudaに統一して2アプリ間での無駄なやりとりを削減。 (11:30頃)
MySQLクエリ解析の結果keyword
を長さ順に全件取得してるのが重い、ということが分かったので、これはとりあえず変更されるものではないしlength
カラムを別に保持してそれで引くようにしよう、と。 (11:45頃)
これだけでそれなりの効果が出て、18,515点で一気に断トツ首位へ。このへんまでの流れ練習通りでとてもスムーズに出来て良かった。
そもそもこのkeyword
の長さ順クエリってhtmlify
時に置換すべきキーワードを探すための正規表現を作るだけのためのものだし、毎回引く必要はなくて正規表現はアプリで保持しておいてentry
が追加・削除されたときだけ(実際にはベンチマークで削除操作は無かったっぽい?)作り直せばいいじゃないか、ということでPOST時に作ってRedisに正規表現文字列を持たせるよう変更。encode_utf8
とかdecode_utf8
を挟まないとRedisに入らない、とかでハマってPerlムズい、ってなったりしたけど、そんなに無駄にハマり続けたりすることもなく完了 (12:40頃)
これもしっかり効果が出て、追いついてきていた2位チームを一気に引き離して 40,321点に。
いまんとこ順調
— すぎゃーん💯 (@sugyan) September 17, 2016
ここまでは良かった…。
当日・後半
ここからtotal_entries
をRedisに載せたり starをRedisに載せる闇改修をmoznionさんが行ったりしている間に、14時過ぎに10万点超えのチームに一気に抜かされ。
やはりhtmlify
の結果そのものをキャッシュしていかないとダメだ、という話になったが、entry
が追加されるごとに正規表現も変更されhtmlify
の結果も変更されないといけないはずだから、単純にはキャッシュできないよな…と悩む(ここでもっと更新されるべき内容を精査するとか考えを巡らせるべきだった)
とにかくアクセスの多いのは/
だから、ここで表示される10件だけなら毎回POSTされるたびに作っても1秒以内のレスポンスは出来るし効果があるでしょう、ということで新規entry
が投稿されるたびに/
で表示される10件だけhtmlify
の結果をRedisに載せて使うように。 (15:10頃)
これで64,434点くらいまでは伸びたが、10万点にはとても届きそうにない…
htmlify
の結果を全部をキャッシュに載せようとするとどうしてもPOST時に関連entry
の結果を更新できそうにない、別プロセスでどうにか出来ないか…?と取り組み始めて、アプリ内でforkしてみたりRedisをJobQueue代わりに使って裏のワーカープロセスでhtmlify
結果を作ってキャッシュに載せる、とかを試みたが、どれも上手くいかずスコアは伸びず…
結局この悪戦苦闘が17時過ぎまでいっても大きな効果を出せず、すべて戻すことに…
その間にサーバ側で様々なチューニングを行ったuzullaさんの変更がプラスされて、再起動テストなどしていたところ 72,018点という結果が出たのでそれを最終スコアとして時間切れで競技終了。
やれるだけのことはやった、が。
— すぎゃーん💯 (@sugyan) September 17, 2016
最終的なコードなどはこちら : https://github.com/uzulla/isucon6-q
反省
予選通過ラインは9〜10万点ということで、どうにもそのラインまで届かせることはできなかった。 序盤で確実にスコアを伸ばしていったのは良かったが、そこから先の大幅な点数アップに辿り着けなかったのは力不足だった。
ある程度のリンク作成ミスがあってもスコア計算としてはとにかく成功GETレスポンスが多ければそのミスを上回る効果があった、という話を後で聞いて、もっとミスを恐れずに貪欲にキャッシュしていくべきだったか、と思ったり。そのへんは試算が足りていなかったし、moznionさんも提案して実装しようとしてくれたところに「元の実装と挙動が変わることになるのは納得いかない」と変なこだわりを持って撥ね除けてしまったのが良くなかった。
そもそもstar
のキャッシュとかで結構元の実装と挙動違う実装にしていてベンチマークを通していたのだからそんなこだわりを捨ててとにかくベンチマークが通って高い得点を出すことができるのなら色々試してみても良かった。
というかキャッシュに関しても「POST時に全部作ってGET時は読むだけ」みたいな形でやり始めて思考停止してしまっていたので良くなかった。「POST時には関連するものだけ破棄して、GET時に作ってキャッシュする」という形をちゃんと想定していたら正しい実装でももっとスコアは伸ばせていたのかもしれない。どういうわけか当日はこういう考えが思い浮かんでいなかった。
感想
取り組み甲斐のある良い問題で、楽しく挑戦することができました。 他の出場者の方々もレベル高く、やはり簡単には勝てないのだな、と痛感いたしました。様々な参加エントリを読んで復習していきたいと思います。
出題・運営の皆様ありがとうございました!
一緒にチームを組んで戦ってくださった id:uzulla さん、 id:moznion さん、ありがとうございました!!