#ISUCON 5の予選をトップ通過してきました
雑に稼ぐにはPerlはサイコーだぜ
tokuhirom
主催のLINE様、毎年ありがとうございます。GCP使いやすかったです、Google様もありがとうございました。
さて、いつもながらfujiwaraさんと組んで1位を取ると、つい自分がすごいと錯覚してしまいそうになるのですが、すごいのはfujiwawaさんであって「僕ではない」ということを強く意識しないといけない。
とはいえ、久しぶりにfujiwaraさんと組んで、自分がどれくらい戦えるのか楽しみだったのですが、以前優勝させてもらった時よりも自分としては大きな手応えがあり、僕もfujiwaraさんと組ませてもらって恥ずかしくないくらいの実力はあるなと思いました。
今年のISUCON予選は、とにかくやることが尽きず、ずっと手を動かしながら改善を続けられる楽しさがあって、疲れましたが充実した時間を過ごすことができました。最近仕事でコード書く量減ってたので、少し不安はあったけど、バリバリ書けてよかった。
これだけやれることが多いと優先順位付けが大変なのですが、とにかく、fujiwaraさんがボトルネックを見つけて「ここが今一番遅いから速くして」って投げられたのを、sugyanと僕とでひたすら並列で改善を続けていったら延々スコアが上がったのですごかった。いやー、この布陣は最強感ありますね。
ということでやったこと。
事前準備
- Slack作っておいた
- Githubはfujiwaraさんが用意してくれた
- chefとかツールをfujiwraさんが事前に入れていた
- 前々日に打ち合わせ
- 言語をPerlで行くことに決めたくらい
- 予選ははてなの東京オフィスの会議室を使うことに
- 休日オフィス開けたことなかったけど開けられてよかった
- ゲスト無線もちゃんと動いてよかった
調査(11:00-12:30)
競技開始。fujiwaraさんがいろいろ整えてくださっている間にアプリを動かしたりソースを読みながら調査。
- テーブル数多いな
- コードも500行超えでこれまでのISUCONに比べてボリューミー
- トップページがとにかくいろいろやっていて遅い
- footprintが毎回INSERTしていて効率悪そう
- 無駄なインデックスが多そう
- インデックスショットガン案件でdrop indexとかしないといけないのかとか思ったけど違った
- データ量が多い
- データ量が多いから初期化処理でデータを流しこむんじゃなくて、大きなデータを削除することで実現してるのなるほど
- kamipo さんが出題関わったんだなって感じがする
アプリを手元で動かすの大変そうだから、p-r上で僕とsugyanでコードをお互いレビューして良さそうだったらマージして本番で動かしますかねーという感じになった。この辺慣れてる言語じゃないと辛いだろうなーとは思ったけど、チーム全員Perlのエキスパートなので問題なかった。
この間コードの変更はないけど、インフラのチューニングでスコアは少々改善。3000点が見えてきた。
スコアは 305 -> 1888
LIMIT 1000の改善とか(12:30-14:00)
トップの LIMIT 1000
2つがボトルネックになってたのでsugyanと僕とでそれぞれ改善。friendsも無駄なクエリとか判定を省いた。
- sugyan:
is_friend
でone
カラムしか見ないように - sugyan:
SELECT * FROM entries ORDER BY created_at DESC LIMIT 1000
の改善 - sugyan:
entries.body
にtitle
カラムを追加 - Songmu:
SELECT * FROM comments ORDER BY created_at DESC LIMIT 1000
の改善 - Songmu: friend一覧取得の
OR
を削る
とりあえず LIMIT 1000
のクエリが遅かったのでそこを直すことに注力した。本番のmysqlシェルでEXPLAIN流しながら調整。他のところも欲張ってJOINしまくったら逆に遅くなったのでとりあえず最低限に留めたりした。
sugyanは entries
テーブルのbodyの巨大さに苦労してて、とりあえず、ALTERしようということになった。ALTERとかデータの移行スクリプトとかめんどくさそうだなーとか思ってたら、fujiwaraさんがSSDインスタンス立てて以下のSQL2発でデータ移行してて超絶だった。
ALTER TABLE entries ADD title VARCHAR(191) NOT NULL DEFAULT '';
UPDATE entries SET title=SUBSTRING_INDEX(body, '\n', 1);
スコアは 1888 -> 9353
タッチの差で、3000点到達を取られて悔しかった。
お昼ごはんと作戦会議(14:00-14:30)
entriesのbodyとtitleの分離ができたので大分やりやすくなった。一旦作戦会議。
- あしあとの処理が重い
- 更新したいが初期データの復元が複雑
- 別テーブルにしたとしてもめんどい
- Redisにしますか
- Redisだったらダンプ取ってそれを /initialize 時に突っ込めば簡単
- タイムスタンプをscoreにしたSorted Setにすれば良さそう
- Songmu担当
- 自分へのコメントに関しては簡単にキャッシュが効かせられそう
- ここもRedis使いますか
- sugyan担当
これが大掛かりなので、最後の一手かもう一手打てるかくらいかなーという感じはしてた。実際、このあしあとの改善が今回の中で一番大きな変更になったので、僕がその改善案を出して担当できたのは良かった。
大改造の時間(14:30-16:00)
- Songmu: Redis::Fast導入
- sugyan:
comments_for_me
を Redisにキャッシュ - fujiwara: 自分のエントリ一覧でbodyを取得しないように
- Songmu: あしあとをRedisにキャッシュ
- Songmu: Redis用初期データ作成のためのエンドポイント作成
Redis::FastはISUCONで追加するモジュールランキングナンバーワン。それと、あしあとの改善でSorted Set使う必要があったので、Redis::LeaderBoardを使うことにした。自分のCPANモジュールがISUCONで役立つの嬉しすぎる。
この時間帯は、インフラ側でやることもだいたい終わっていたので、fujiwaraさんもコード書いて細かい改善をしていた。それができるのでfujiwara組強すぎる。
動作確認とバグ取り(16:00-17:30)
すんなりとは動かなくてデバッグに苦労した。具体的には以下の問題があって動かなくなっていた。
- コメントの文字化け(30分ロス)
- あしあと付ける対象を逆にしてた(1時間ロス)
ここが一番苦労した時間帯だった。本番デバッグは辛い。
スコアは 9353 -> 12389
大きくは改善せず。上位には残っているものの、GoBoldは2万点超えで遥か上。とは言え、複雑なところは大分ほどいたので、後の改善はやりやすくなったはず。
ラストスパート(17:30-18:15)
この辺になるとuserを引くクエリがネックになっているとfujiwaraさんが指摘。確かに呼ばれる回数は多いなーとはコード見ては思っていた。変更はないからキャッシュしようということに。親プロセスでのプリロードも考えたけど、無難にRedisに保存するのとプロセスキャッシュ。担当はsugyan。僕は別の場所のN+1クエリを潰したりしていた。userがキャッシュされるのでJOINが必要じゃなくなったところも増えたので助かった。
- sugyan: ユーザーデータを Redisにキャッシュ
- sugyan: Redisにキャッシュしたデータをオンザフライでプロセスにもキャッシュ
- Songmu: プロフィールページでis_friendの判定が3回走っていたのを1回に
- Songmu:
comments_of_friends
におけるentryのN+1クエリをWHERE IN
で一発で取るように
スコアは 12389 -> 26338
正直GoBoldを捲るのは厳しんじゃないかと少し思ってたんだけど、結果的にぶち抜けて気持ちよかった。
スコア提出(18:15-19:00)
もう一個p-rを残してたんだけど、スコア的には大分2位に差をつけてるし、ここでコードフリーズしようということに。
fujiwaraさんが、Redisのダンプを取って、それを /initialize で復元する処理を書いて終了。 /initialize も2秒で終わって平和。
再起動試験も念入りに行なって、スコア27232でフィニッシュ。予選突破できるか失格にならないか不安はやっぱあったけど、通過できてよかった。本戦も頑張って優勝しようと思います。
あわせて読みたい
- ソースコード: https://github.com/fujiwara/isucon5q
- fujiwaraさん: ISUCON5予選を全体1位で通過しました
- sugyan: #isucon 2015予選に参加した